Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "mozilla/DebugOnly.h" // for DebugOnly |
michael@0 | 7 | |
michael@0 | 8 | #include <stdio.h> // for nullptr, stdout |
michael@0 | 9 | #include <string.h> // for strcmp |
michael@0 | 10 | |
michael@0 | 11 | #include "ChangeAttributeTxn.h" // for ChangeAttributeTxn |
michael@0 | 12 | #include "CreateElementTxn.h" // for CreateElementTxn |
michael@0 | 13 | #include "DeleteNodeTxn.h" // for DeleteNodeTxn |
michael@0 | 14 | #include "DeleteRangeTxn.h" // for DeleteRangeTxn |
michael@0 | 15 | #include "DeleteTextTxn.h" // for DeleteTextTxn |
michael@0 | 16 | #include "EditAggregateTxn.h" // for EditAggregateTxn |
michael@0 | 17 | #include "EditTxn.h" // for EditTxn |
michael@0 | 18 | #include "IMETextTxn.h" // for IMETextTxn |
michael@0 | 19 | #include "InsertElementTxn.h" // for InsertElementTxn |
michael@0 | 20 | #include "InsertTextTxn.h" // for InsertTextTxn |
michael@0 | 21 | #include "JoinElementTxn.h" // for JoinElementTxn |
michael@0 | 22 | #include "PlaceholderTxn.h" // for PlaceholderTxn |
michael@0 | 23 | #include "SplitElementTxn.h" // for SplitElementTxn |
michael@0 | 24 | #include "mozFlushType.h" // for mozFlushType::Flush_Frames |
michael@0 | 25 | #include "mozISpellCheckingEngine.h" |
michael@0 | 26 | #include "mozInlineSpellChecker.h" // for mozInlineSpellChecker |
michael@0 | 27 | #include "mozilla/IMEStateManager.h" // for IMEStateManager |
michael@0 | 28 | #include "mozilla/Preferences.h" // for Preferences |
michael@0 | 29 | #include "mozilla/dom/Selection.h" // for Selection, etc |
michael@0 | 30 | #include "mozilla/Services.h" // for GetObserverService |
michael@0 | 31 | #include "mozilla/TextComposition.h" // for TextComposition |
michael@0 | 32 | #include "mozilla/TextEvents.h" |
michael@0 | 33 | #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement |
michael@0 | 34 | #include "mozilla/mozalloc.h" // for operator new, etc |
michael@0 | 35 | #include "nsAString.h" // for nsAString_internal::Length, etc |
michael@0 | 36 | #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker |
michael@0 | 37 | #include "nsCaret.h" // for nsCaret |
michael@0 | 38 | #include "nsCaseTreatment.h" |
michael@0 | 39 | #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc |
michael@0 | 40 | #include "nsComponentManagerUtils.h" // for do_CreateInstance |
michael@0 | 41 | #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle |
michael@0 | 42 | #include "nsContentUtils.h" // for nsContentUtils |
michael@0 | 43 | #include "nsDOMString.h" // for DOMStringIsNull |
michael@0 | 44 | #include "nsDebug.h" // for NS_ENSURE_TRUE, etc |
michael@0 | 45 | #include "nsEditProperty.h" // for nsEditProperty, etc |
michael@0 | 46 | #include "nsEditor.h" |
michael@0 | 47 | #include "nsEditorEventListener.h" // for nsEditorEventListener |
michael@0 | 48 | #include "nsEditorUtils.h" // for nsAutoRules, etc |
michael@0 | 49 | #include "nsError.h" // for NS_OK, etc |
michael@0 | 50 | #include "nsFocusManager.h" // for nsFocusManager |
michael@0 | 51 | #include "nsFrameSelection.h" // for nsFrameSelection |
michael@0 | 52 | #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir |
michael@0 | 53 | #include "nsIAbsorbingTransaction.h" // for nsIAbsorbingTransaction |
michael@0 | 54 | #include "nsIAtom.h" // for nsIAtom |
michael@0 | 55 | #include "nsIContent.h" // for nsIContent |
michael@0 | 56 | #include "nsIDOMAttr.h" // for nsIDOMAttr |
michael@0 | 57 | #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData |
michael@0 | 58 | #include "nsIDOMDocument.h" // for nsIDOMDocument |
michael@0 | 59 | #include "nsIDOMElement.h" // for nsIDOMElement |
michael@0 | 60 | #include "nsIDOMEvent.h" // for nsIDOMEvent |
michael@0 | 61 | #include "nsIDOMEventListener.h" // for nsIDOMEventListener |
michael@0 | 62 | #include "nsIDOMEventTarget.h" // for nsIDOMEventTarget |
michael@0 | 63 | #include "nsIDOMHTMLElement.h" // for nsIDOMHTMLElement |
michael@0 | 64 | #include "nsIDOMKeyEvent.h" // for nsIDOMKeyEvent, etc |
michael@0 | 65 | #include "nsIDOMMozNamedAttrMap.h" // for nsIDOMMozNamedAttrMap |
michael@0 | 66 | #include "nsIDOMMouseEvent.h" // for nsIDOMMouseEvent |
michael@0 | 67 | #include "nsIDOMNode.h" // for nsIDOMNode, etc |
michael@0 | 68 | #include "nsIDOMNodeList.h" // for nsIDOMNodeList |
michael@0 | 69 | #include "nsIDOMRange.h" // for nsIDOMRange |
michael@0 | 70 | #include "nsIDOMText.h" // for nsIDOMText |
michael@0 | 71 | #include "nsIDocument.h" // for nsIDocument |
michael@0 | 72 | #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener |
michael@0 | 73 | #include "nsIEditActionListener.h" // for nsIEditActionListener |
michael@0 | 74 | #include "nsIEditorObserver.h" // for nsIEditorObserver |
michael@0 | 75 | #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck |
michael@0 | 76 | #include "nsIFrame.h" // for nsIFrame |
michael@0 | 77 | #include "nsIHTMLDocument.h" // for nsIHTMLDocument |
michael@0 | 78 | #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc |
michael@0 | 79 | #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc |
michael@0 | 80 | #include "nsINode.h" // for nsINode, etc |
michael@0 | 81 | #include "nsIObserverService.h" // for nsIObserverService |
michael@0 | 82 | #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc |
michael@0 | 83 | #include "nsIPresShell.h" // for nsIPresShell |
michael@0 | 84 | #include "nsISelection.h" // for nsISelection, etc |
michael@0 | 85 | #include "nsISelectionController.h" // for nsISelectionController, etc |
michael@0 | 86 | #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc |
michael@0 | 87 | #include "nsISelectionPrivate.h" // for nsISelectionPrivate, etc |
michael@0 | 88 | #include "nsISupportsBase.h" // for nsISupports |
michael@0 | 89 | #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF |
michael@0 | 90 | #include "nsITransaction.h" // for nsITransaction |
michael@0 | 91 | #include "nsITransactionManager.h" |
michael@0 | 92 | #include "nsIWeakReference.h" // for nsISupportsWeakReference |
michael@0 | 93 | #include "nsIWidget.h" // for nsIWidget, IMEState, etc |
michael@0 | 94 | #include "nsPIDOMWindow.h" // for nsPIDOMWindow |
michael@0 | 95 | #include "nsPresContext.h" // for nsPresContext |
michael@0 | 96 | #include "nsRange.h" // for nsRange |
michael@0 | 97 | #include "nsReadableUtils.h" // for EmptyString, ToNewCString |
michael@0 | 98 | #include "nsString.h" // for nsAutoString, nsString, etc |
michael@0 | 99 | #include "nsStringFwd.h" // for nsAFlatString |
michael@0 | 100 | #include "nsStyleConsts.h" // for NS_STYLE_DIRECTION_RTL, etc |
michael@0 | 101 | #include "nsStyleContext.h" // for nsStyleContext |
michael@0 | 102 | #include "nsStyleSheetTxns.h" // for AddStyleSheetTxn, etc |
michael@0 | 103 | #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc |
michael@0 | 104 | #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc |
michael@0 | 105 | #include "nsTextEditUtils.h" // for nsTextEditUtils |
michael@0 | 106 | #include "nsTextNode.h" // for nsTextNode |
michael@0 | 107 | #include "nsThreadUtils.h" // for nsRunnable |
michael@0 | 108 | #include "nsTransactionManager.h" // for nsTransactionManager |
michael@0 | 109 | #include "prtime.h" // for PR_Now |
michael@0 | 110 | |
michael@0 | 111 | class nsIOutputStream; |
michael@0 | 112 | class nsIParserService; |
michael@0 | 113 | class nsITransferable; |
michael@0 | 114 | |
michael@0 | 115 | #ifdef DEBUG |
michael@0 | 116 | #include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument |
michael@0 | 117 | #endif |
michael@0 | 118 | |
michael@0 | 119 | using namespace mozilla; |
michael@0 | 120 | using namespace mozilla::dom; |
michael@0 | 121 | using namespace mozilla::widget; |
michael@0 | 122 | |
michael@0 | 123 | // Defined in nsEditorRegistration.cpp |
michael@0 | 124 | extern nsIParserService *sParserService; |
michael@0 | 125 | |
michael@0 | 126 | //--------------------------------------------------------------------------- |
michael@0 | 127 | // |
michael@0 | 128 | // nsEditor: base editor class implementation |
michael@0 | 129 | // |
michael@0 | 130 | //--------------------------------------------------------------------------- |
michael@0 | 131 | |
michael@0 | 132 | nsEditor::nsEditor() |
michael@0 | 133 | : mPlaceHolderName(nullptr) |
michael@0 | 134 | , mSelState(nullptr) |
michael@0 | 135 | , mPhonetic(nullptr) |
michael@0 | 136 | , mModCount(0) |
michael@0 | 137 | , mFlags(0) |
michael@0 | 138 | , mUpdateCount(0) |
michael@0 | 139 | , mPlaceHolderBatch(0) |
michael@0 | 140 | , mAction(EditAction::none) |
michael@0 | 141 | , mIMETextOffset(0) |
michael@0 | 142 | , mDirection(eNone) |
michael@0 | 143 | , mDocDirtyState(-1) |
michael@0 | 144 | , mSpellcheckCheckboxState(eTriUnset) |
michael@0 | 145 | , mShouldTxnSetSelection(true) |
michael@0 | 146 | , mDidPreDestroy(false) |
michael@0 | 147 | , mDidPostCreate(false) |
michael@0 | 148 | , mDispatchInputEvent(true) |
michael@0 | 149 | { |
michael@0 | 150 | } |
michael@0 | 151 | |
michael@0 | 152 | nsEditor::~nsEditor() |
michael@0 | 153 | { |
michael@0 | 154 | NS_ASSERTION(!mDocWeak || mDidPreDestroy, "Why PreDestroy hasn't been called?"); |
michael@0 | 155 | |
michael@0 | 156 | mTxnMgr = nullptr; |
michael@0 | 157 | |
michael@0 | 158 | delete mPhonetic; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | NS_IMPL_CYCLE_COLLECTION_CLASS(nsEditor) |
michael@0 | 162 | |
michael@0 | 163 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsEditor) |
michael@0 | 164 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement) |
michael@0 | 165 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker) |
michael@0 | 166 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mTxnMgr) |
michael@0 | 167 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMETextNode) |
michael@0 | 168 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners) |
michael@0 | 169 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorObservers) |
michael@0 | 170 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners) |
michael@0 | 171 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget) |
michael@0 | 172 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventListener) |
michael@0 | 173 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
michael@0 | 174 | |
michael@0 | 175 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsEditor) |
michael@0 | 176 | nsIDocument* currentDoc = |
michael@0 | 177 | tmp->mRootElement ? tmp->mRootElement->GetCurrentDoc() : nullptr; |
michael@0 | 178 | if (currentDoc && |
michael@0 | 179 | nsCCUncollectableMarker::InGeneration(cb, currentDoc->GetMarkedCCGeneration())) { |
michael@0 | 180 | return NS_SUCCESS_INTERRUPTED_TRAVERSE; |
michael@0 | 181 | } |
michael@0 | 182 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement) |
michael@0 | 183 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker) |
michael@0 | 184 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTxnMgr) |
michael@0 | 185 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMETextNode) |
michael@0 | 186 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners) |
michael@0 | 187 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorObservers) |
michael@0 | 188 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners) |
michael@0 | 189 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget) |
michael@0 | 190 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener) |
michael@0 | 191 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
michael@0 | 192 | |
michael@0 | 193 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEditor) |
michael@0 | 194 | NS_INTERFACE_MAP_ENTRY(nsIPhonetic) |
michael@0 | 195 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
michael@0 | 196 | NS_INTERFACE_MAP_ENTRY(nsIEditorIMESupport) |
michael@0 | 197 | NS_INTERFACE_MAP_ENTRY(nsIEditor) |
michael@0 | 198 | NS_INTERFACE_MAP_ENTRY(nsIObserver) |
michael@0 | 199 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor) |
michael@0 | 200 | NS_INTERFACE_MAP_END |
michael@0 | 201 | |
michael@0 | 202 | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditor) |
michael@0 | 203 | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditor) |
michael@0 | 204 | |
michael@0 | 205 | |
michael@0 | 206 | NS_IMETHODIMP |
michael@0 | 207 | nsEditor::Init(nsIDOMDocument *aDoc, nsIContent *aRoot, |
michael@0 | 208 | nsISelectionController *aSelCon, uint32_t aFlags, |
michael@0 | 209 | const nsAString& aValue) |
michael@0 | 210 | { |
michael@0 | 211 | NS_PRECONDITION(aDoc, "bad arg"); |
michael@0 | 212 | if (!aDoc) |
michael@0 | 213 | return NS_ERROR_NULL_POINTER; |
michael@0 | 214 | |
michael@0 | 215 | // First only set flags, but other stuff shouldn't be initialized now. |
michael@0 | 216 | // Don't move this call after initializing mDocWeak. |
michael@0 | 217 | // SetFlags() can check whether it's called during initialization or not by |
michael@0 | 218 | // them. Note that SetFlags() will be called by PostCreate(). |
michael@0 | 219 | #ifdef DEBUG |
michael@0 | 220 | nsresult rv = |
michael@0 | 221 | #endif |
michael@0 | 222 | SetFlags(aFlags); |
michael@0 | 223 | NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed"); |
michael@0 | 224 | |
michael@0 | 225 | mDocWeak = do_GetWeakReference(aDoc); // weak reference to doc |
michael@0 | 226 | // HTML editors currently don't have their own selection controller, |
michael@0 | 227 | // so they'll pass null as aSelCon, and we'll get the selection controller |
michael@0 | 228 | // off of the presshell. |
michael@0 | 229 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 230 | if (aSelCon) { |
michael@0 | 231 | mSelConWeak = do_GetWeakReference(aSelCon); // weak reference to selectioncontroller |
michael@0 | 232 | selCon = aSelCon; |
michael@0 | 233 | } else { |
michael@0 | 234 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 235 | selCon = do_QueryInterface(presShell); |
michael@0 | 236 | } |
michael@0 | 237 | NS_ASSERTION(selCon, "Selection controller should be available at this point"); |
michael@0 | 238 | |
michael@0 | 239 | //set up root element if we are passed one. |
michael@0 | 240 | if (aRoot) |
michael@0 | 241 | mRootElement = do_QueryInterface(aRoot); |
michael@0 | 242 | |
michael@0 | 243 | mUpdateCount=0; |
michael@0 | 244 | |
michael@0 | 245 | /* initialize IME stuff */ |
michael@0 | 246 | mIMETextNode = nullptr; |
michael@0 | 247 | mIMETextOffset = 0; |
michael@0 | 248 | /* Show the caret */ |
michael@0 | 249 | selCon->SetCaretReadOnly(false); |
michael@0 | 250 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); |
michael@0 | 251 | |
michael@0 | 252 | selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);//we want to see all the selection reflected to user |
michael@0 | 253 | |
michael@0 | 254 | NS_POSTCONDITION(mDocWeak, "bad state"); |
michael@0 | 255 | |
michael@0 | 256 | // Make sure that the editor will be destroyed properly |
michael@0 | 257 | mDidPreDestroy = false; |
michael@0 | 258 | // Make sure that the ediotr will be created properly |
michael@0 | 259 | mDidPostCreate = false; |
michael@0 | 260 | |
michael@0 | 261 | return NS_OK; |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | |
michael@0 | 265 | NS_IMETHODIMP |
michael@0 | 266 | nsEditor::PostCreate() |
michael@0 | 267 | { |
michael@0 | 268 | // Synchronize some stuff for the flags. SetFlags() will initialize |
michael@0 | 269 | // something by the flag difference. This is first time of that, so, all |
michael@0 | 270 | // initializations must be run. For such reason, we need to invert mFlags |
michael@0 | 271 | // value first. |
michael@0 | 272 | mFlags = ~mFlags; |
michael@0 | 273 | nsresult rv = SetFlags(~mFlags); |
michael@0 | 274 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 275 | |
michael@0 | 276 | // These operations only need to happen on the first PostCreate call |
michael@0 | 277 | if (!mDidPostCreate) { |
michael@0 | 278 | mDidPostCreate = true; |
michael@0 | 279 | |
michael@0 | 280 | // Set up listeners |
michael@0 | 281 | CreateEventListeners(); |
michael@0 | 282 | rv = InstallEventListeners(); |
michael@0 | 283 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 284 | |
michael@0 | 285 | // nuke the modification count, so the doc appears unmodified |
michael@0 | 286 | // do this before we notify listeners |
michael@0 | 287 | ResetModificationCount(); |
michael@0 | 288 | |
michael@0 | 289 | // update the UI with our state |
michael@0 | 290 | NotifyDocumentListeners(eDocumentCreated); |
michael@0 | 291 | NotifyDocumentListeners(eDocumentStateChanged); |
michael@0 | 292 | |
michael@0 | 293 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
michael@0 | 294 | if (obs) { |
michael@0 | 295 | obs->AddObserver(this, |
michael@0 | 296 | SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION, |
michael@0 | 297 | false); |
michael@0 | 298 | } |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | // update nsTextStateManager and caret if we have focus |
michael@0 | 302 | nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); |
michael@0 | 303 | if (focusedContent) { |
michael@0 | 304 | nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(focusedContent); |
michael@0 | 305 | if (target) { |
michael@0 | 306 | InitializeSelection(target); |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | // If the text control gets reframed during focus, Focus() would not be |
michael@0 | 310 | // called, so take a chance here to see if we need to spell check the text |
michael@0 | 311 | // control. |
michael@0 | 312 | nsEditorEventListener* listener = |
michael@0 | 313 | reinterpret_cast<nsEditorEventListener*> (mEventListener.get()); |
michael@0 | 314 | listener->SpellCheckIfNeeded(); |
michael@0 | 315 | |
michael@0 | 316 | IMEState newState; |
michael@0 | 317 | rv = GetPreferredIMEState(&newState); |
michael@0 | 318 | NS_ENSURE_SUCCESS(rv, NS_OK); |
michael@0 | 319 | nsCOMPtr<nsIContent> content = GetFocusedContentForIME(); |
michael@0 | 320 | IMEStateManager::UpdateIMEState(newState, content); |
michael@0 | 321 | } |
michael@0 | 322 | return NS_OK; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | /* virtual */ |
michael@0 | 326 | void |
michael@0 | 327 | nsEditor::CreateEventListeners() |
michael@0 | 328 | { |
michael@0 | 329 | // Don't create the handler twice |
michael@0 | 330 | if (!mEventListener) { |
michael@0 | 331 | mEventListener = new nsEditorEventListener(); |
michael@0 | 332 | } |
michael@0 | 333 | } |
michael@0 | 334 | |
michael@0 | 335 | nsresult |
michael@0 | 336 | nsEditor::InstallEventListeners() |
michael@0 | 337 | { |
michael@0 | 338 | NS_ENSURE_TRUE(mDocWeak && mEventListener, |
michael@0 | 339 | NS_ERROR_NOT_INITIALIZED); |
michael@0 | 340 | |
michael@0 | 341 | // Initialize the event target. |
michael@0 | 342 | nsCOMPtr<nsIContent> rootContent = GetRoot(); |
michael@0 | 343 | NS_ENSURE_TRUE(rootContent, NS_ERROR_NOT_AVAILABLE); |
michael@0 | 344 | mEventTarget = do_QueryInterface(rootContent->GetParent()); |
michael@0 | 345 | NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_AVAILABLE); |
michael@0 | 346 | |
michael@0 | 347 | nsEditorEventListener* listener = |
michael@0 | 348 | reinterpret_cast<nsEditorEventListener*>(mEventListener.get()); |
michael@0 | 349 | return listener->Connect(this); |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | void |
michael@0 | 353 | nsEditor::RemoveEventListeners() |
michael@0 | 354 | { |
michael@0 | 355 | if (!mDocWeak || !mEventListener) { |
michael@0 | 356 | return; |
michael@0 | 357 | } |
michael@0 | 358 | reinterpret_cast<nsEditorEventListener*>(mEventListener.get())->Disconnect(); |
michael@0 | 359 | if (mComposition) { |
michael@0 | 360 | mComposition->EndHandlingComposition(this); |
michael@0 | 361 | mComposition = nullptr; |
michael@0 | 362 | } |
michael@0 | 363 | mEventTarget = nullptr; |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | bool |
michael@0 | 367 | nsEditor::GetDesiredSpellCheckState() |
michael@0 | 368 | { |
michael@0 | 369 | // Check user override on this element |
michael@0 | 370 | if (mSpellcheckCheckboxState != eTriUnset) { |
michael@0 | 371 | return (mSpellcheckCheckboxState == eTriTrue); |
michael@0 | 372 | } |
michael@0 | 373 | |
michael@0 | 374 | // Check user preferences |
michael@0 | 375 | int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1); |
michael@0 | 376 | |
michael@0 | 377 | if (spellcheckLevel == 0) { |
michael@0 | 378 | return false; // Spellchecking forced off globally |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | if (!CanEnableSpellCheck()) { |
michael@0 | 382 | return false; |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 386 | if (presShell) { |
michael@0 | 387 | nsPresContext* context = presShell->GetPresContext(); |
michael@0 | 388 | if (context && !context->IsDynamic()) { |
michael@0 | 389 | return false; |
michael@0 | 390 | } |
michael@0 | 391 | } |
michael@0 | 392 | |
michael@0 | 393 | // Check DOM state |
michael@0 | 394 | nsCOMPtr<nsIContent> content = GetExposedRoot(); |
michael@0 | 395 | if (!content) { |
michael@0 | 396 | return false; |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(content); |
michael@0 | 400 | if (!element) { |
michael@0 | 401 | return false; |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | if (!IsPlaintextEditor()) { |
michael@0 | 405 | // Some of the page content might be editable and some not, if spellcheck= |
michael@0 | 406 | // is explicitly set anywhere, so if there's anything editable on the page, |
michael@0 | 407 | // return true and let the spellchecker figure it out. |
michael@0 | 408 | nsCOMPtr<nsIHTMLDocument> doc = do_QueryInterface(content->GetCurrentDoc()); |
michael@0 | 409 | return doc && doc->IsEditingOn(); |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | bool enable; |
michael@0 | 413 | element->GetSpellcheck(&enable); |
michael@0 | 414 | |
michael@0 | 415 | return enable; |
michael@0 | 416 | } |
michael@0 | 417 | |
michael@0 | 418 | NS_IMETHODIMP |
michael@0 | 419 | nsEditor::PreDestroy(bool aDestroyingFrames) |
michael@0 | 420 | { |
michael@0 | 421 | if (mDidPreDestroy) |
michael@0 | 422 | return NS_OK; |
michael@0 | 423 | |
michael@0 | 424 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
michael@0 | 425 | if (obs) { |
michael@0 | 426 | obs->RemoveObserver(this, |
michael@0 | 427 | SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION); |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | // Let spellchecker clean up its observers etc. It is important not to |
michael@0 | 431 | // actually free the spellchecker here, since the spellchecker could have |
michael@0 | 432 | // caused flush notifications, which could have gotten here if a textbox |
michael@0 | 433 | // is being removed. Setting the spellchecker to nullptr could free the |
michael@0 | 434 | // object that is still in use! It will be freed when the editor is |
michael@0 | 435 | // destroyed. |
michael@0 | 436 | if (mInlineSpellChecker) |
michael@0 | 437 | mInlineSpellChecker->Cleanup(aDestroyingFrames); |
michael@0 | 438 | |
michael@0 | 439 | // tell our listeners that the doc is going away |
michael@0 | 440 | NotifyDocumentListeners(eDocumentToBeDestroyed); |
michael@0 | 441 | |
michael@0 | 442 | // Unregister event listeners |
michael@0 | 443 | RemoveEventListeners(); |
michael@0 | 444 | mActionListeners.Clear(); |
michael@0 | 445 | mEditorObservers.Clear(); |
michael@0 | 446 | mDocStateListeners.Clear(); |
michael@0 | 447 | mInlineSpellChecker = nullptr; |
michael@0 | 448 | mSpellcheckCheckboxState = eTriUnset; |
michael@0 | 449 | mRootElement = nullptr; |
michael@0 | 450 | |
michael@0 | 451 | mDidPreDestroy = true; |
michael@0 | 452 | return NS_OK; |
michael@0 | 453 | } |
michael@0 | 454 | |
michael@0 | 455 | NS_IMETHODIMP |
michael@0 | 456 | nsEditor::GetFlags(uint32_t *aFlags) |
michael@0 | 457 | { |
michael@0 | 458 | *aFlags = mFlags; |
michael@0 | 459 | return NS_OK; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | NS_IMETHODIMP |
michael@0 | 463 | nsEditor::SetFlags(uint32_t aFlags) |
michael@0 | 464 | { |
michael@0 | 465 | if (mFlags == aFlags) { |
michael@0 | 466 | return NS_OK; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | bool spellcheckerWasEnabled = CanEnableSpellCheck(); |
michael@0 | 470 | mFlags = aFlags; |
michael@0 | 471 | |
michael@0 | 472 | if (!mDocWeak) { |
michael@0 | 473 | // If we're initializing, we shouldn't do anything now. |
michael@0 | 474 | // SetFlags() will be called by PostCreate(), |
michael@0 | 475 | // we should synchronize some stuff for the flags at that time. |
michael@0 | 476 | return NS_OK; |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | // The flag change may cause the spellchecker state change |
michael@0 | 480 | if (CanEnableSpellCheck() != spellcheckerWasEnabled) { |
michael@0 | 481 | nsresult rv = SyncRealTimeSpell(); |
michael@0 | 482 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | // If this is called from PostCreate(), it will update the IME state if it's |
michael@0 | 486 | // necessary. |
michael@0 | 487 | if (!mDidPostCreate) { |
michael@0 | 488 | return NS_OK; |
michael@0 | 489 | } |
michael@0 | 490 | |
michael@0 | 491 | // Might be changing editable state, so, we need to reset current IME state |
michael@0 | 492 | // if we're focused and the flag change causes IME state change. |
michael@0 | 493 | nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); |
michael@0 | 494 | if (focusedContent) { |
michael@0 | 495 | IMEState newState; |
michael@0 | 496 | nsresult rv = GetPreferredIMEState(&newState); |
michael@0 | 497 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 498 | // NOTE: When the enabled state isn't going to be modified, this method |
michael@0 | 499 | // is going to do nothing. |
michael@0 | 500 | nsCOMPtr<nsIContent> content = GetFocusedContentForIME(); |
michael@0 | 501 | IMEStateManager::UpdateIMEState(newState, content); |
michael@0 | 502 | } |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | return NS_OK; |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | NS_IMETHODIMP |
michael@0 | 509 | nsEditor::GetIsSelectionEditable(bool *aIsSelectionEditable) |
michael@0 | 510 | { |
michael@0 | 511 | NS_ENSURE_ARG_POINTER(aIsSelectionEditable); |
michael@0 | 512 | |
michael@0 | 513 | // get current selection |
michael@0 | 514 | nsCOMPtr<nsISelection> selection; |
michael@0 | 515 | nsresult res = GetSelection(getter_AddRefs(selection)); |
michael@0 | 516 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 517 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 518 | |
michael@0 | 519 | // XXX we just check that the anchor node is editable at the moment |
michael@0 | 520 | // we should check that all nodes in the selection are editable |
michael@0 | 521 | nsCOMPtr<nsIDOMNode> anchorNode; |
michael@0 | 522 | selection->GetAnchorNode(getter_AddRefs(anchorNode)); |
michael@0 | 523 | *aIsSelectionEditable = anchorNode && IsEditable(anchorNode); |
michael@0 | 524 | |
michael@0 | 525 | return NS_OK; |
michael@0 | 526 | } |
michael@0 | 527 | |
michael@0 | 528 | NS_IMETHODIMP |
michael@0 | 529 | nsEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) |
michael@0 | 530 | { |
michael@0 | 531 | NS_ENSURE_ARG_POINTER(aIsDocumentEditable); |
michael@0 | 532 | nsCOMPtr<nsIDOMDocument> doc = GetDOMDocument(); |
michael@0 | 533 | *aIsDocumentEditable = !!doc; |
michael@0 | 534 | |
michael@0 | 535 | return NS_OK; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | already_AddRefed<nsIDocument> |
michael@0 | 539 | nsEditor::GetDocument() |
michael@0 | 540 | { |
michael@0 | 541 | NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized"); |
michael@0 | 542 | nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 543 | return doc.forget(); |
michael@0 | 544 | } |
michael@0 | 545 | |
michael@0 | 546 | already_AddRefed<nsIDOMDocument> |
michael@0 | 547 | nsEditor::GetDOMDocument() |
michael@0 | 548 | { |
michael@0 | 549 | NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized"); |
michael@0 | 550 | nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 551 | return doc.forget(); |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | NS_IMETHODIMP |
michael@0 | 555 | nsEditor::GetDocument(nsIDOMDocument **aDoc) |
michael@0 | 556 | { |
michael@0 | 557 | nsCOMPtr<nsIDOMDocument> doc = GetDOMDocument(); |
michael@0 | 558 | doc.forget(aDoc); |
michael@0 | 559 | return *aDoc ? NS_OK : NS_ERROR_NOT_INITIALIZED; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | already_AddRefed<nsIPresShell> |
michael@0 | 563 | nsEditor::GetPresShell() |
michael@0 | 564 | { |
michael@0 | 565 | NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak"); |
michael@0 | 566 | nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 567 | NS_ENSURE_TRUE(doc, nullptr); |
michael@0 | 568 | nsCOMPtr<nsIPresShell> ps = doc->GetShell(); |
michael@0 | 569 | return ps.forget(); |
michael@0 | 570 | } |
michael@0 | 571 | |
michael@0 | 572 | already_AddRefed<nsIWidget> |
michael@0 | 573 | nsEditor::GetWidget() |
michael@0 | 574 | { |
michael@0 | 575 | nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
michael@0 | 576 | NS_ENSURE_TRUE(ps, nullptr); |
michael@0 | 577 | nsPresContext* pc = ps->GetPresContext(); |
michael@0 | 578 | NS_ENSURE_TRUE(pc, nullptr); |
michael@0 | 579 | nsCOMPtr<nsIWidget> widget = pc->GetRootWidget(); |
michael@0 | 580 | NS_ENSURE_TRUE(widget.get(), nullptr); |
michael@0 | 581 | return widget.forget(); |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | /* attribute string contentsMIMEType; */ |
michael@0 | 585 | NS_IMETHODIMP |
michael@0 | 586 | nsEditor::GetContentsMIMEType(char * *aContentsMIMEType) |
michael@0 | 587 | { |
michael@0 | 588 | NS_ENSURE_ARG_POINTER(aContentsMIMEType); |
michael@0 | 589 | *aContentsMIMEType = ToNewCString(mContentMIMEType); |
michael@0 | 590 | return NS_OK; |
michael@0 | 591 | } |
michael@0 | 592 | |
michael@0 | 593 | NS_IMETHODIMP |
michael@0 | 594 | nsEditor::SetContentsMIMEType(const char * aContentsMIMEType) |
michael@0 | 595 | { |
michael@0 | 596 | mContentMIMEType.Assign(aContentsMIMEType ? aContentsMIMEType : ""); |
michael@0 | 597 | return NS_OK; |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | NS_IMETHODIMP |
michael@0 | 601 | nsEditor::GetSelectionController(nsISelectionController **aSel) |
michael@0 | 602 | { |
michael@0 | 603 | NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER); |
michael@0 | 604 | *aSel = nullptr; // init out param |
michael@0 | 605 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 606 | if (mSelConWeak) { |
michael@0 | 607 | selCon = do_QueryReferent(mSelConWeak); |
michael@0 | 608 | } else { |
michael@0 | 609 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 610 | selCon = do_QueryInterface(presShell); |
michael@0 | 611 | } |
michael@0 | 612 | NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 613 | NS_ADDREF(*aSel = selCon); |
michael@0 | 614 | return NS_OK; |
michael@0 | 615 | } |
michael@0 | 616 | |
michael@0 | 617 | |
michael@0 | 618 | NS_IMETHODIMP |
michael@0 | 619 | nsEditor::DeleteSelection(EDirection aAction, EStripWrappers aStripWrappers) |
michael@0 | 620 | { |
michael@0 | 621 | MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
michael@0 | 622 | return DeleteSelectionImpl(aAction, aStripWrappers); |
michael@0 | 623 | } |
michael@0 | 624 | |
michael@0 | 625 | |
michael@0 | 626 | |
michael@0 | 627 | NS_IMETHODIMP |
michael@0 | 628 | nsEditor::GetSelection(nsISelection **aSelection) |
michael@0 | 629 | { |
michael@0 | 630 | NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); |
michael@0 | 631 | *aSelection = nullptr; |
michael@0 | 632 | nsCOMPtr<nsISelectionController> selcon; |
michael@0 | 633 | GetSelectionController(getter_AddRefs(selcon)); |
michael@0 | 634 | NS_ENSURE_TRUE(selcon, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 635 | return selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, aSelection); // does an addref |
michael@0 | 636 | } |
michael@0 | 637 | |
michael@0 | 638 | Selection* |
michael@0 | 639 | nsEditor::GetSelection() |
michael@0 | 640 | { |
michael@0 | 641 | nsCOMPtr<nsISelection> sel; |
michael@0 | 642 | nsresult res = GetSelection(getter_AddRefs(sel)); |
michael@0 | 643 | NS_ENSURE_SUCCESS(res, nullptr); |
michael@0 | 644 | |
michael@0 | 645 | return static_cast<Selection*>(sel.get()); |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | NS_IMETHODIMP |
michael@0 | 649 | nsEditor::DoTransaction(nsITransaction* aTxn) |
michael@0 | 650 | { |
michael@0 | 651 | if (mPlaceHolderBatch && !mPlaceHolderTxn) { |
michael@0 | 652 | nsCOMPtr<nsIAbsorbingTransaction> plcTxn = new PlaceholderTxn(); |
michael@0 | 653 | |
michael@0 | 654 | // save off weak reference to placeholder txn |
michael@0 | 655 | mPlaceHolderTxn = do_GetWeakReference(plcTxn); |
michael@0 | 656 | plcTxn->Init(mPlaceHolderName, mSelState, this); |
michael@0 | 657 | // placeholder txn took ownership of this pointer |
michael@0 | 658 | mSelState = nullptr; |
michael@0 | 659 | |
michael@0 | 660 | // QI to an nsITransaction since that's what DoTransaction() expects |
michael@0 | 661 | nsCOMPtr<nsITransaction> theTxn = do_QueryInterface(plcTxn); |
michael@0 | 662 | // we will recurse, but will not hit this case in the nested call |
michael@0 | 663 | DoTransaction(theTxn); |
michael@0 | 664 | |
michael@0 | 665 | if (mTxnMgr) { |
michael@0 | 666 | nsCOMPtr<nsITransaction> topTxn = mTxnMgr->PeekUndoStack(); |
michael@0 | 667 | if (topTxn) { |
michael@0 | 668 | plcTxn = do_QueryInterface(topTxn); |
michael@0 | 669 | if (plcTxn) { |
michael@0 | 670 | // there is a placeholder transaction on top of the undo stack. It |
michael@0 | 671 | // is either the one we just created, or an earlier one that we are |
michael@0 | 672 | // now merging into. From here on out remember this placeholder |
michael@0 | 673 | // instead of the one we just created. |
michael@0 | 674 | mPlaceHolderTxn = do_GetWeakReference(plcTxn); |
michael@0 | 675 | } |
michael@0 | 676 | } |
michael@0 | 677 | } |
michael@0 | 678 | } |
michael@0 | 679 | |
michael@0 | 680 | if (aTxn) { |
michael@0 | 681 | // XXX: Why are we doing selection specific batching stuff here? |
michael@0 | 682 | // XXX: Most entry points into the editor have auto variables that |
michael@0 | 683 | // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make |
michael@0 | 684 | // XXX: these selection batch calls no-ops. |
michael@0 | 685 | // XXX: |
michael@0 | 686 | // XXX: I suspect that this was placed here to avoid multiple |
michael@0 | 687 | // XXX: selection changed notifications from happening until after |
michael@0 | 688 | // XXX: the transaction was done. I suppose that can still happen |
michael@0 | 689 | // XXX: if an embedding application called DoTransaction() directly |
michael@0 | 690 | // XXX: to pump its own transactions through the system, but in that |
michael@0 | 691 | // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or |
michael@0 | 692 | // XXX: its auto equivalent nsAutoUpdateViewBatch to ensure that |
michael@0 | 693 | // XXX: selection listeners have access to accurate frame data? |
michael@0 | 694 | // XXX: |
michael@0 | 695 | // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls |
michael@0 | 696 | // XXX: we will need to make sure that they are disabled during |
michael@0 | 697 | // XXX: the init of the editor for text widgets to avoid layout |
michael@0 | 698 | // XXX: re-entry during initial reflow. - kin |
michael@0 | 699 | |
michael@0 | 700 | // get the selection and start a batch change |
michael@0 | 701 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 702 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 703 | |
michael@0 | 704 | selection->StartBatchChanges(); |
michael@0 | 705 | |
michael@0 | 706 | nsresult res; |
michael@0 | 707 | if (mTxnMgr) { |
michael@0 | 708 | res = mTxnMgr->DoTransaction(aTxn); |
michael@0 | 709 | } else { |
michael@0 | 710 | res = aTxn->DoTransaction(); |
michael@0 | 711 | } |
michael@0 | 712 | if (NS_SUCCEEDED(res)) { |
michael@0 | 713 | DoAfterDoTransaction(aTxn); |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | // no need to check res here, don't lose result of operation |
michael@0 | 717 | selection->EndBatchChanges(); |
michael@0 | 718 | |
michael@0 | 719 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | return NS_OK; |
michael@0 | 723 | } |
michael@0 | 724 | |
michael@0 | 725 | |
michael@0 | 726 | NS_IMETHODIMP |
michael@0 | 727 | nsEditor::EnableUndo(bool aEnable) |
michael@0 | 728 | { |
michael@0 | 729 | if (aEnable) { |
michael@0 | 730 | if (!mTxnMgr) { |
michael@0 | 731 | mTxnMgr = new nsTransactionManager(); |
michael@0 | 732 | } |
michael@0 | 733 | mTxnMgr->SetMaxTransactionCount(-1); |
michael@0 | 734 | } else if (mTxnMgr) { |
michael@0 | 735 | // disable the transaction manager if it is enabled |
michael@0 | 736 | mTxnMgr->Clear(); |
michael@0 | 737 | mTxnMgr->SetMaxTransactionCount(0); |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | return NS_OK; |
michael@0 | 741 | } |
michael@0 | 742 | |
michael@0 | 743 | NS_IMETHODIMP |
michael@0 | 744 | nsEditor::GetNumberOfUndoItems(int32_t* aNumItems) |
michael@0 | 745 | { |
michael@0 | 746 | *aNumItems = 0; |
michael@0 | 747 | return mTxnMgr ? mTxnMgr->GetNumberOfUndoItems(aNumItems) : NS_OK; |
michael@0 | 748 | } |
michael@0 | 749 | |
michael@0 | 750 | NS_IMETHODIMP |
michael@0 | 751 | nsEditor::GetNumberOfRedoItems(int32_t* aNumItems) |
michael@0 | 752 | { |
michael@0 | 753 | *aNumItems = 0; |
michael@0 | 754 | return mTxnMgr ? mTxnMgr->GetNumberOfRedoItems(aNumItems) : NS_OK; |
michael@0 | 755 | } |
michael@0 | 756 | |
michael@0 | 757 | NS_IMETHODIMP |
michael@0 | 758 | nsEditor::GetTransactionManager(nsITransactionManager* *aTxnManager) |
michael@0 | 759 | { |
michael@0 | 760 | NS_ENSURE_ARG_POINTER(aTxnManager); |
michael@0 | 761 | |
michael@0 | 762 | *aTxnManager = nullptr; |
michael@0 | 763 | NS_ENSURE_TRUE(mTxnMgr, NS_ERROR_FAILURE); |
michael@0 | 764 | |
michael@0 | 765 | NS_ADDREF(*aTxnManager = mTxnMgr); |
michael@0 | 766 | return NS_OK; |
michael@0 | 767 | } |
michael@0 | 768 | |
michael@0 | 769 | NS_IMETHODIMP |
michael@0 | 770 | nsEditor::SetTransactionManager(nsITransactionManager *aTxnManager) |
michael@0 | 771 | { |
michael@0 | 772 | NS_ENSURE_TRUE(aTxnManager, NS_ERROR_FAILURE); |
michael@0 | 773 | |
michael@0 | 774 | // nsITransactionManager is builtinclass, so this is safe |
michael@0 | 775 | mTxnMgr = static_cast<nsTransactionManager*>(aTxnManager); |
michael@0 | 776 | return NS_OK; |
michael@0 | 777 | } |
michael@0 | 778 | |
michael@0 | 779 | NS_IMETHODIMP |
michael@0 | 780 | nsEditor::Undo(uint32_t aCount) |
michael@0 | 781 | { |
michael@0 | 782 | ForceCompositionEnd(); |
michael@0 | 783 | |
michael@0 | 784 | bool hasTxnMgr, hasTransaction = false; |
michael@0 | 785 | CanUndo(&hasTxnMgr, &hasTransaction); |
michael@0 | 786 | NS_ENSURE_TRUE(hasTransaction, NS_OK); |
michael@0 | 787 | |
michael@0 | 788 | nsAutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone); |
michael@0 | 789 | |
michael@0 | 790 | if (!mTxnMgr) { |
michael@0 | 791 | return NS_OK; |
michael@0 | 792 | } |
michael@0 | 793 | |
michael@0 | 794 | for (uint32_t i = 0; i < aCount; ++i) { |
michael@0 | 795 | nsresult rv = mTxnMgr->UndoTransaction(); |
michael@0 | 796 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 797 | |
michael@0 | 798 | DoAfterUndoTransaction(); |
michael@0 | 799 | } |
michael@0 | 800 | |
michael@0 | 801 | return NS_OK; |
michael@0 | 802 | } |
michael@0 | 803 | |
michael@0 | 804 | |
michael@0 | 805 | NS_IMETHODIMP nsEditor::CanUndo(bool *aIsEnabled, bool *aCanUndo) |
michael@0 | 806 | { |
michael@0 | 807 | NS_ENSURE_TRUE(aIsEnabled && aCanUndo, NS_ERROR_NULL_POINTER); |
michael@0 | 808 | *aIsEnabled = !!mTxnMgr; |
michael@0 | 809 | if (*aIsEnabled) { |
michael@0 | 810 | int32_t numTxns = 0; |
michael@0 | 811 | mTxnMgr->GetNumberOfUndoItems(&numTxns); |
michael@0 | 812 | *aCanUndo = !!numTxns; |
michael@0 | 813 | } else { |
michael@0 | 814 | *aCanUndo = false; |
michael@0 | 815 | } |
michael@0 | 816 | return NS_OK; |
michael@0 | 817 | } |
michael@0 | 818 | |
michael@0 | 819 | |
michael@0 | 820 | NS_IMETHODIMP |
michael@0 | 821 | nsEditor::Redo(uint32_t aCount) |
michael@0 | 822 | { |
michael@0 | 823 | bool hasTxnMgr, hasTransaction = false; |
michael@0 | 824 | CanRedo(&hasTxnMgr, &hasTransaction); |
michael@0 | 825 | NS_ENSURE_TRUE(hasTransaction, NS_OK); |
michael@0 | 826 | |
michael@0 | 827 | nsAutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone); |
michael@0 | 828 | |
michael@0 | 829 | if (!mTxnMgr) { |
michael@0 | 830 | return NS_OK; |
michael@0 | 831 | } |
michael@0 | 832 | |
michael@0 | 833 | for (uint32_t i = 0; i < aCount; ++i) { |
michael@0 | 834 | nsresult rv = mTxnMgr->RedoTransaction(); |
michael@0 | 835 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 836 | |
michael@0 | 837 | DoAfterRedoTransaction(); |
michael@0 | 838 | } |
michael@0 | 839 | |
michael@0 | 840 | return NS_OK; |
michael@0 | 841 | } |
michael@0 | 842 | |
michael@0 | 843 | |
michael@0 | 844 | NS_IMETHODIMP nsEditor::CanRedo(bool *aIsEnabled, bool *aCanRedo) |
michael@0 | 845 | { |
michael@0 | 846 | NS_ENSURE_TRUE(aIsEnabled && aCanRedo, NS_ERROR_NULL_POINTER); |
michael@0 | 847 | |
michael@0 | 848 | *aIsEnabled = !!mTxnMgr; |
michael@0 | 849 | if (*aIsEnabled) { |
michael@0 | 850 | int32_t numTxns = 0; |
michael@0 | 851 | mTxnMgr->GetNumberOfRedoItems(&numTxns); |
michael@0 | 852 | *aCanRedo = !!numTxns; |
michael@0 | 853 | } else { |
michael@0 | 854 | *aCanRedo = false; |
michael@0 | 855 | } |
michael@0 | 856 | return NS_OK; |
michael@0 | 857 | } |
michael@0 | 858 | |
michael@0 | 859 | |
michael@0 | 860 | NS_IMETHODIMP |
michael@0 | 861 | nsEditor::BeginTransaction() |
michael@0 | 862 | { |
michael@0 | 863 | BeginUpdateViewBatch(); |
michael@0 | 864 | |
michael@0 | 865 | if (mTxnMgr) { |
michael@0 | 866 | mTxnMgr->BeginBatch(nullptr); |
michael@0 | 867 | } |
michael@0 | 868 | |
michael@0 | 869 | return NS_OK; |
michael@0 | 870 | } |
michael@0 | 871 | |
michael@0 | 872 | NS_IMETHODIMP |
michael@0 | 873 | nsEditor::EndTransaction() |
michael@0 | 874 | { |
michael@0 | 875 | if (mTxnMgr) { |
michael@0 | 876 | mTxnMgr->EndBatch(false); |
michael@0 | 877 | } |
michael@0 | 878 | |
michael@0 | 879 | EndUpdateViewBatch(); |
michael@0 | 880 | |
michael@0 | 881 | return NS_OK; |
michael@0 | 882 | } |
michael@0 | 883 | |
michael@0 | 884 | |
michael@0 | 885 | // These two routines are similar to the above, but do not use |
michael@0 | 886 | // the transaction managers batching feature. Instead we use |
michael@0 | 887 | // a placeholder transaction to wrap up any further transaction |
michael@0 | 888 | // while the batch is open. The advantage of this is that |
michael@0 | 889 | // placeholder transactions can later merge, if needed. Merging |
michael@0 | 890 | // is unavailable between transaction manager batches. |
michael@0 | 891 | |
michael@0 | 892 | NS_IMETHODIMP |
michael@0 | 893 | nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName) |
michael@0 | 894 | { |
michael@0 | 895 | NS_PRECONDITION(mPlaceHolderBatch >= 0, "negative placeholder batch count!"); |
michael@0 | 896 | if (!mPlaceHolderBatch) |
michael@0 | 897 | { |
michael@0 | 898 | // time to turn on the batch |
michael@0 | 899 | BeginUpdateViewBatch(); |
michael@0 | 900 | mPlaceHolderTxn = nullptr; |
michael@0 | 901 | mPlaceHolderName = aName; |
michael@0 | 902 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 903 | if (selection) { |
michael@0 | 904 | mSelState = new nsSelectionState(); |
michael@0 | 905 | mSelState->SaveSelection(selection); |
michael@0 | 906 | } |
michael@0 | 907 | } |
michael@0 | 908 | mPlaceHolderBatch++; |
michael@0 | 909 | |
michael@0 | 910 | return NS_OK; |
michael@0 | 911 | } |
michael@0 | 912 | |
michael@0 | 913 | NS_IMETHODIMP |
michael@0 | 914 | nsEditor::EndPlaceHolderTransaction() |
michael@0 | 915 | { |
michael@0 | 916 | NS_PRECONDITION(mPlaceHolderBatch > 0, "zero or negative placeholder batch count when ending batch!"); |
michael@0 | 917 | if (mPlaceHolderBatch == 1) |
michael@0 | 918 | { |
michael@0 | 919 | nsCOMPtr<nsISelection>selection; |
michael@0 | 920 | GetSelection(getter_AddRefs(selection)); |
michael@0 | 921 | |
michael@0 | 922 | nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection)); |
michael@0 | 923 | |
michael@0 | 924 | // By making the assumption that no reflow happens during the calls |
michael@0 | 925 | // to EndUpdateViewBatch and ScrollSelectionIntoView, we are able to |
michael@0 | 926 | // allow the selection to cache a frame offset which is used by the |
michael@0 | 927 | // caret drawing code. We only enable this cache here; at other times, |
michael@0 | 928 | // we have no way to know whether reflow invalidates it |
michael@0 | 929 | // See bugs 35296 and 199412. |
michael@0 | 930 | if (selPrivate) { |
michael@0 | 931 | selPrivate->SetCanCacheFrameOffset(true); |
michael@0 | 932 | } |
michael@0 | 933 | |
michael@0 | 934 | { |
michael@0 | 935 | // Hide the caret here to avoid hiding it twice, once in EndUpdateViewBatch |
michael@0 | 936 | // and once in ScrollSelectionIntoView. |
michael@0 | 937 | nsRefPtr<nsCaret> caret; |
michael@0 | 938 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 939 | |
michael@0 | 940 | if (presShell) |
michael@0 | 941 | caret = presShell->GetCaret(); |
michael@0 | 942 | |
michael@0 | 943 | // time to turn off the batch |
michael@0 | 944 | EndUpdateViewBatch(); |
michael@0 | 945 | // make sure selection is in view |
michael@0 | 946 | |
michael@0 | 947 | // After ScrollSelectionIntoView(), the pending notifications might be |
michael@0 | 948 | // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. |
michael@0 | 949 | ScrollSelectionIntoView(false); |
michael@0 | 950 | } |
michael@0 | 951 | |
michael@0 | 952 | // cached for frame offset are Not available now |
michael@0 | 953 | if (selPrivate) { |
michael@0 | 954 | selPrivate->SetCanCacheFrameOffset(false); |
michael@0 | 955 | } |
michael@0 | 956 | |
michael@0 | 957 | if (mSelState) |
michael@0 | 958 | { |
michael@0 | 959 | // we saved the selection state, but never got to hand it to placeholder |
michael@0 | 960 | // (else we ould have nulled out this pointer), so destroy it to prevent leaks. |
michael@0 | 961 | delete mSelState; |
michael@0 | 962 | mSelState = nullptr; |
michael@0 | 963 | } |
michael@0 | 964 | if (mPlaceHolderTxn) // we might have never made a placeholder if no action took place |
michael@0 | 965 | { |
michael@0 | 966 | nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryReferent(mPlaceHolderTxn); |
michael@0 | 967 | if (plcTxn) |
michael@0 | 968 | { |
michael@0 | 969 | plcTxn->EndPlaceHolderBatch(); |
michael@0 | 970 | } |
michael@0 | 971 | else |
michael@0 | 972 | { |
michael@0 | 973 | // in the future we will check to make sure undo is off here, |
michael@0 | 974 | // since that is the only known case where the placeholdertxn would disappear on us. |
michael@0 | 975 | // For now just removing the assert. |
michael@0 | 976 | } |
michael@0 | 977 | // notify editor observers of action but if composing, it's done by |
michael@0 | 978 | // text event handler. |
michael@0 | 979 | if (!mComposition) { |
michael@0 | 980 | NotifyEditorObservers(); |
michael@0 | 981 | } |
michael@0 | 982 | } |
michael@0 | 983 | } |
michael@0 | 984 | mPlaceHolderBatch--; |
michael@0 | 985 | |
michael@0 | 986 | return NS_OK; |
michael@0 | 987 | } |
michael@0 | 988 | |
michael@0 | 989 | NS_IMETHODIMP |
michael@0 | 990 | nsEditor::ShouldTxnSetSelection(bool *aResult) |
michael@0 | 991 | { |
michael@0 | 992 | NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER); |
michael@0 | 993 | *aResult = mShouldTxnSetSelection; |
michael@0 | 994 | return NS_OK; |
michael@0 | 995 | } |
michael@0 | 996 | |
michael@0 | 997 | NS_IMETHODIMP |
michael@0 | 998 | nsEditor::SetShouldTxnSetSelection(bool aShould) |
michael@0 | 999 | { |
michael@0 | 1000 | mShouldTxnSetSelection = aShould; |
michael@0 | 1001 | return NS_OK; |
michael@0 | 1002 | } |
michael@0 | 1003 | |
michael@0 | 1004 | NS_IMETHODIMP |
michael@0 | 1005 | nsEditor::GetDocumentIsEmpty(bool *aDocumentIsEmpty) |
michael@0 | 1006 | { |
michael@0 | 1007 | *aDocumentIsEmpty = true; |
michael@0 | 1008 | |
michael@0 | 1009 | dom::Element* root = GetRoot(); |
michael@0 | 1010 | NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); |
michael@0 | 1011 | |
michael@0 | 1012 | *aDocumentIsEmpty = !root->HasChildren(); |
michael@0 | 1013 | return NS_OK; |
michael@0 | 1014 | } |
michael@0 | 1015 | |
michael@0 | 1016 | |
michael@0 | 1017 | // XXX: the rule system should tell us which node to select all on (ie, the root, or the body) |
michael@0 | 1018 | NS_IMETHODIMP nsEditor::SelectAll() |
michael@0 | 1019 | { |
michael@0 | 1020 | if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } |
michael@0 | 1021 | ForceCompositionEnd(); |
michael@0 | 1022 | |
michael@0 | 1023 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 1024 | GetSelectionController(getter_AddRefs(selCon)); |
michael@0 | 1025 | NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1026 | nsCOMPtr<nsISelection> selection; |
michael@0 | 1027 | nsresult result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); |
michael@0 | 1028 | if (NS_SUCCEEDED(result) && selection) |
michael@0 | 1029 | { |
michael@0 | 1030 | result = SelectEntireDocument(selection); |
michael@0 | 1031 | } |
michael@0 | 1032 | return result; |
michael@0 | 1033 | } |
michael@0 | 1034 | |
michael@0 | 1035 | NS_IMETHODIMP nsEditor::BeginningOfDocument() |
michael@0 | 1036 | { |
michael@0 | 1037 | if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } |
michael@0 | 1038 | |
michael@0 | 1039 | // get the selection |
michael@0 | 1040 | nsCOMPtr<nsISelection> selection; |
michael@0 | 1041 | nsresult result = GetSelection(getter_AddRefs(selection)); |
michael@0 | 1042 | NS_ENSURE_SUCCESS(result, result); |
michael@0 | 1043 | NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1044 | |
michael@0 | 1045 | // get the root element |
michael@0 | 1046 | dom::Element* rootElement = GetRoot(); |
michael@0 | 1047 | NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); |
michael@0 | 1048 | |
michael@0 | 1049 | // find first editable thingy |
michael@0 | 1050 | nsCOMPtr<nsINode> firstNode = GetFirstEditableNode(rootElement); |
michael@0 | 1051 | if (!firstNode) { |
michael@0 | 1052 | // just the root node, set selection to inside the root |
michael@0 | 1053 | return selection->CollapseNative(rootElement, 0); |
michael@0 | 1054 | } |
michael@0 | 1055 | |
michael@0 | 1056 | if (firstNode->NodeType() == nsIDOMNode::TEXT_NODE) { |
michael@0 | 1057 | // If firstNode is text, set selection to beginning of the text node. |
michael@0 | 1058 | return selection->CollapseNative(firstNode, 0); |
michael@0 | 1059 | } |
michael@0 | 1060 | |
michael@0 | 1061 | // Otherwise, it's a leaf node and we set the selection just in front of it. |
michael@0 | 1062 | nsCOMPtr<nsIContent> parent = firstNode->GetParent(); |
michael@0 | 1063 | if (!parent) { |
michael@0 | 1064 | return NS_ERROR_NULL_POINTER; |
michael@0 | 1065 | } |
michael@0 | 1066 | |
michael@0 | 1067 | int32_t offsetInParent = parent->IndexOf(firstNode); |
michael@0 | 1068 | return selection->CollapseNative(parent, offsetInParent); |
michael@0 | 1069 | } |
michael@0 | 1070 | |
michael@0 | 1071 | NS_IMETHODIMP |
michael@0 | 1072 | nsEditor::EndOfDocument() |
michael@0 | 1073 | { |
michael@0 | 1074 | NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1075 | |
michael@0 | 1076 | // get selection |
michael@0 | 1077 | nsCOMPtr<nsISelection> selection; |
michael@0 | 1078 | nsresult rv = GetSelection(getter_AddRefs(selection)); |
michael@0 | 1079 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1080 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 1081 | |
michael@0 | 1082 | // get the root element |
michael@0 | 1083 | nsINode* node = GetRoot(); |
michael@0 | 1084 | NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
michael@0 | 1085 | nsINode* child = node->GetLastChild(); |
michael@0 | 1086 | |
michael@0 | 1087 | while (child && IsContainer(child->AsDOMNode())) { |
michael@0 | 1088 | node = child; |
michael@0 | 1089 | child = node->GetLastChild(); |
michael@0 | 1090 | } |
michael@0 | 1091 | |
michael@0 | 1092 | uint32_t length = node->Length(); |
michael@0 | 1093 | return selection->CollapseNative(node, int32_t(length)); |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | NS_IMETHODIMP |
michael@0 | 1097 | nsEditor::GetDocumentModified(bool *outDocModified) |
michael@0 | 1098 | { |
michael@0 | 1099 | NS_ENSURE_TRUE(outDocModified, NS_ERROR_NULL_POINTER); |
michael@0 | 1100 | |
michael@0 | 1101 | int32_t modCount = 0; |
michael@0 | 1102 | GetModificationCount(&modCount); |
michael@0 | 1103 | |
michael@0 | 1104 | *outDocModified = (modCount != 0); |
michael@0 | 1105 | return NS_OK; |
michael@0 | 1106 | } |
michael@0 | 1107 | |
michael@0 | 1108 | NS_IMETHODIMP |
michael@0 | 1109 | nsEditor::GetDocumentCharacterSet(nsACString &characterSet) |
michael@0 | 1110 | { |
michael@0 | 1111 | nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 1112 | NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); |
michael@0 | 1113 | |
michael@0 | 1114 | characterSet = doc->GetDocumentCharacterSet(); |
michael@0 | 1115 | return NS_OK; |
michael@0 | 1116 | } |
michael@0 | 1117 | |
michael@0 | 1118 | NS_IMETHODIMP |
michael@0 | 1119 | nsEditor::SetDocumentCharacterSet(const nsACString& characterSet) |
michael@0 | 1120 | { |
michael@0 | 1121 | nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 1122 | NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); |
michael@0 | 1123 | |
michael@0 | 1124 | doc->SetDocumentCharacterSet(characterSet); |
michael@0 | 1125 | return NS_OK; |
michael@0 | 1126 | } |
michael@0 | 1127 | |
michael@0 | 1128 | NS_IMETHODIMP |
michael@0 | 1129 | nsEditor::Cut() |
michael@0 | 1130 | { |
michael@0 | 1131 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1132 | } |
michael@0 | 1133 | |
michael@0 | 1134 | NS_IMETHODIMP |
michael@0 | 1135 | nsEditor::CanCut(bool *aCanCut) |
michael@0 | 1136 | { |
michael@0 | 1137 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1138 | } |
michael@0 | 1139 | |
michael@0 | 1140 | NS_IMETHODIMP |
michael@0 | 1141 | nsEditor::Copy() |
michael@0 | 1142 | { |
michael@0 | 1143 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1144 | } |
michael@0 | 1145 | |
michael@0 | 1146 | NS_IMETHODIMP |
michael@0 | 1147 | nsEditor::CanCopy(bool *aCanCut) |
michael@0 | 1148 | { |
michael@0 | 1149 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1150 | } |
michael@0 | 1151 | |
michael@0 | 1152 | NS_IMETHODIMP |
michael@0 | 1153 | nsEditor::Paste(int32_t aSelectionType) |
michael@0 | 1154 | { |
michael@0 | 1155 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1156 | } |
michael@0 | 1157 | |
michael@0 | 1158 | NS_IMETHODIMP |
michael@0 | 1159 | nsEditor::PasteTransferable(nsITransferable *aTransferable) |
michael@0 | 1160 | { |
michael@0 | 1161 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1162 | } |
michael@0 | 1163 | |
michael@0 | 1164 | NS_IMETHODIMP |
michael@0 | 1165 | nsEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) |
michael@0 | 1166 | { |
michael@0 | 1167 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | NS_IMETHODIMP |
michael@0 | 1171 | nsEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) |
michael@0 | 1172 | { |
michael@0 | 1173 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1174 | } |
michael@0 | 1175 | |
michael@0 | 1176 | NS_IMETHODIMP |
michael@0 | 1177 | nsEditor::SetAttribute(nsIDOMElement *aElement, const nsAString & aAttribute, const nsAString & aValue) |
michael@0 | 1178 | { |
michael@0 | 1179 | nsRefPtr<ChangeAttributeTxn> txn; |
michael@0 | 1180 | nsresult result = CreateTxnForSetAttribute(aElement, aAttribute, aValue, |
michael@0 | 1181 | getter_AddRefs(txn)); |
michael@0 | 1182 | if (NS_SUCCEEDED(result)) { |
michael@0 | 1183 | result = DoTransaction(txn); |
michael@0 | 1184 | } |
michael@0 | 1185 | return result; |
michael@0 | 1186 | } |
michael@0 | 1187 | |
michael@0 | 1188 | NS_IMETHODIMP |
michael@0 | 1189 | nsEditor::GetAttributeValue(nsIDOMElement *aElement, |
michael@0 | 1190 | const nsAString & aAttribute, |
michael@0 | 1191 | nsAString & aResultValue, |
michael@0 | 1192 | bool *aResultIsSet) |
michael@0 | 1193 | { |
michael@0 | 1194 | NS_ENSURE_TRUE(aResultIsSet, NS_ERROR_NULL_POINTER); |
michael@0 | 1195 | *aResultIsSet = false; |
michael@0 | 1196 | if (!aElement) { |
michael@0 | 1197 | return NS_OK; |
michael@0 | 1198 | } |
michael@0 | 1199 | nsAutoString value; |
michael@0 | 1200 | nsresult rv = aElement->GetAttribute(aAttribute, value); |
michael@0 | 1201 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1202 | if (!DOMStringIsNull(value)) { |
michael@0 | 1203 | *aResultIsSet = true; |
michael@0 | 1204 | aResultValue = value; |
michael@0 | 1205 | } |
michael@0 | 1206 | return rv; |
michael@0 | 1207 | } |
michael@0 | 1208 | |
michael@0 | 1209 | NS_IMETHODIMP |
michael@0 | 1210 | nsEditor::RemoveAttribute(nsIDOMElement *aElement, const nsAString& aAttribute) |
michael@0 | 1211 | { |
michael@0 | 1212 | nsRefPtr<ChangeAttributeTxn> txn; |
michael@0 | 1213 | nsresult result = CreateTxnForRemoveAttribute(aElement, aAttribute, |
michael@0 | 1214 | getter_AddRefs(txn)); |
michael@0 | 1215 | if (NS_SUCCEEDED(result)) { |
michael@0 | 1216 | result = DoTransaction(txn); |
michael@0 | 1217 | } |
michael@0 | 1218 | return result; |
michael@0 | 1219 | } |
michael@0 | 1220 | |
michael@0 | 1221 | |
michael@0 | 1222 | bool |
michael@0 | 1223 | nsEditor::OutputsMozDirty() |
michael@0 | 1224 | { |
michael@0 | 1225 | // Return true for Composer (!eEditorAllowInteraction) or mail |
michael@0 | 1226 | // (eEditorMailMask), but false for webpages. |
michael@0 | 1227 | return !(mFlags & nsIPlaintextEditor::eEditorAllowInteraction) || |
michael@0 | 1228 | (mFlags & nsIPlaintextEditor::eEditorMailMask); |
michael@0 | 1229 | } |
michael@0 | 1230 | |
michael@0 | 1231 | |
michael@0 | 1232 | NS_IMETHODIMP |
michael@0 | 1233 | nsEditor::MarkNodeDirty(nsIDOMNode* aNode) |
michael@0 | 1234 | { |
michael@0 | 1235 | // Mark the node dirty, but not for webpages (bug 599983) |
michael@0 | 1236 | if (!OutputsMozDirty()) { |
michael@0 | 1237 | return NS_OK; |
michael@0 | 1238 | } |
michael@0 | 1239 | nsCOMPtr<dom::Element> element = do_QueryInterface(aNode); |
michael@0 | 1240 | if (element) { |
michael@0 | 1241 | element->SetAttr(kNameSpaceID_None, nsEditProperty::mozdirty, |
michael@0 | 1242 | EmptyString(), false); |
michael@0 | 1243 | } |
michael@0 | 1244 | return NS_OK; |
michael@0 | 1245 | } |
michael@0 | 1246 | |
michael@0 | 1247 | NS_IMETHODIMP nsEditor::GetInlineSpellChecker(bool autoCreate, |
michael@0 | 1248 | nsIInlineSpellChecker ** aInlineSpellChecker) |
michael@0 | 1249 | { |
michael@0 | 1250 | NS_ENSURE_ARG_POINTER(aInlineSpellChecker); |
michael@0 | 1251 | |
michael@0 | 1252 | if (mDidPreDestroy) { |
michael@0 | 1253 | // Don't allow people to get or create the spell checker once the editor |
michael@0 | 1254 | // is going away. |
michael@0 | 1255 | *aInlineSpellChecker = nullptr; |
michael@0 | 1256 | return autoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK; |
michael@0 | 1257 | } |
michael@0 | 1258 | |
michael@0 | 1259 | // We don't want to show the spell checking UI if there are no spell check dictionaries available. |
michael@0 | 1260 | bool canSpell = mozInlineSpellChecker::CanEnableInlineSpellChecking(); |
michael@0 | 1261 | if (!canSpell) { |
michael@0 | 1262 | *aInlineSpellChecker = nullptr; |
michael@0 | 1263 | return NS_ERROR_FAILURE; |
michael@0 | 1264 | } |
michael@0 | 1265 | |
michael@0 | 1266 | nsresult rv; |
michael@0 | 1267 | if (!mInlineSpellChecker && autoCreate) { |
michael@0 | 1268 | mInlineSpellChecker = do_CreateInstance(MOZ_INLINESPELLCHECKER_CONTRACTID, &rv); |
michael@0 | 1269 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1270 | } |
michael@0 | 1271 | |
michael@0 | 1272 | if (mInlineSpellChecker) { |
michael@0 | 1273 | rv = mInlineSpellChecker->Init(this); |
michael@0 | 1274 | if (NS_FAILED(rv)) |
michael@0 | 1275 | mInlineSpellChecker = nullptr; |
michael@0 | 1276 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1277 | } |
michael@0 | 1278 | |
michael@0 | 1279 | NS_IF_ADDREF(*aInlineSpellChecker = mInlineSpellChecker); |
michael@0 | 1280 | |
michael@0 | 1281 | return NS_OK; |
michael@0 | 1282 | } |
michael@0 | 1283 | |
michael@0 | 1284 | NS_IMETHODIMP nsEditor::Observe(nsISupports* aSubj, const char *aTopic, |
michael@0 | 1285 | const char16_t *aData) |
michael@0 | 1286 | { |
michael@0 | 1287 | NS_ASSERTION(!strcmp(aTopic, |
michael@0 | 1288 | SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION), |
michael@0 | 1289 | "Unexpected observer topic"); |
michael@0 | 1290 | |
michael@0 | 1291 | // When mozInlineSpellChecker::CanEnableInlineSpellChecking changes |
michael@0 | 1292 | SyncRealTimeSpell(); |
michael@0 | 1293 | |
michael@0 | 1294 | // When nsIEditorSpellCheck::GetCurrentDictionary changes |
michael@0 | 1295 | if (mInlineSpellChecker) { |
michael@0 | 1296 | // if the current dictionary is no longer available, find another one |
michael@0 | 1297 | nsCOMPtr<nsIEditorSpellCheck> editorSpellCheck; |
michael@0 | 1298 | mInlineSpellChecker->GetSpellChecker(getter_AddRefs(editorSpellCheck)); |
michael@0 | 1299 | if (editorSpellCheck) { |
michael@0 | 1300 | // Note: This might change the current dictionary, which may call |
michael@0 | 1301 | // this observer recursively. |
michael@0 | 1302 | editorSpellCheck->CheckCurrentDictionary(); |
michael@0 | 1303 | } |
michael@0 | 1304 | |
michael@0 | 1305 | // update the inline spell checker to reflect the new current dictionary |
michael@0 | 1306 | mInlineSpellChecker->SpellCheckRange(nullptr); // causes recheck |
michael@0 | 1307 | } |
michael@0 | 1308 | |
michael@0 | 1309 | return NS_OK; |
michael@0 | 1310 | } |
michael@0 | 1311 | |
michael@0 | 1312 | NS_IMETHODIMP nsEditor::SyncRealTimeSpell() |
michael@0 | 1313 | { |
michael@0 | 1314 | bool enable = GetDesiredSpellCheckState(); |
michael@0 | 1315 | |
michael@0 | 1316 | // Initializes mInlineSpellChecker |
michael@0 | 1317 | nsCOMPtr<nsIInlineSpellChecker> spellChecker; |
michael@0 | 1318 | GetInlineSpellChecker(enable, getter_AddRefs(spellChecker)); |
michael@0 | 1319 | |
michael@0 | 1320 | if (mInlineSpellChecker) { |
michael@0 | 1321 | // We might have a mInlineSpellChecker even if there are no dictionaries |
michael@0 | 1322 | // available since we don't destroy the mInlineSpellChecker when the last |
michael@0 | 1323 | // dictionariy is removed, but in that case spellChecker is null |
michael@0 | 1324 | mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker); |
michael@0 | 1325 | } |
michael@0 | 1326 | |
michael@0 | 1327 | return NS_OK; |
michael@0 | 1328 | } |
michael@0 | 1329 | |
michael@0 | 1330 | NS_IMETHODIMP nsEditor::SetSpellcheckUserOverride(bool enable) |
michael@0 | 1331 | { |
michael@0 | 1332 | mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse; |
michael@0 | 1333 | |
michael@0 | 1334 | return SyncRealTimeSpell(); |
michael@0 | 1335 | } |
michael@0 | 1336 | |
michael@0 | 1337 | NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag, |
michael@0 | 1338 | nsIDOMNode * aParent, |
michael@0 | 1339 | int32_t aPosition, |
michael@0 | 1340 | nsIDOMNode ** aNewNode) |
michael@0 | 1341 | { |
michael@0 | 1342 | int32_t i; |
michael@0 | 1343 | |
michael@0 | 1344 | nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext); |
michael@0 | 1345 | |
michael@0 | 1346 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1347 | mActionListeners[i]->WillCreateNode(aTag, aParent, aPosition); |
michael@0 | 1348 | |
michael@0 | 1349 | nsRefPtr<CreateElementTxn> txn; |
michael@0 | 1350 | nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition, |
michael@0 | 1351 | getter_AddRefs(txn)); |
michael@0 | 1352 | if (NS_SUCCEEDED(result)) |
michael@0 | 1353 | { |
michael@0 | 1354 | result = DoTransaction(txn); |
michael@0 | 1355 | if (NS_SUCCEEDED(result)) |
michael@0 | 1356 | { |
michael@0 | 1357 | result = txn->GetNewNode(aNewNode); |
michael@0 | 1358 | NS_ASSERTION((NS_SUCCEEDED(result)), "GetNewNode can't fail if txn::DoTransaction succeeded."); |
michael@0 | 1359 | } |
michael@0 | 1360 | } |
michael@0 | 1361 | |
michael@0 | 1362 | mRangeUpdater.SelAdjCreateNode(aParent, aPosition); |
michael@0 | 1363 | |
michael@0 | 1364 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1365 | mActionListeners[i]->DidCreateNode(aTag, *aNewNode, aParent, aPosition, result); |
michael@0 | 1366 | |
michael@0 | 1367 | return result; |
michael@0 | 1368 | } |
michael@0 | 1369 | |
michael@0 | 1370 | |
michael@0 | 1371 | NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode, |
michael@0 | 1372 | nsIDOMNode * aParent, |
michael@0 | 1373 | int32_t aPosition) |
michael@0 | 1374 | { |
michael@0 | 1375 | int32_t i; |
michael@0 | 1376 | nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
michael@0 | 1377 | |
michael@0 | 1378 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1379 | mActionListeners[i]->WillInsertNode(aNode, aParent, aPosition); |
michael@0 | 1380 | |
michael@0 | 1381 | nsRefPtr<InsertElementTxn> txn; |
michael@0 | 1382 | nsresult result = CreateTxnForInsertElement(aNode, aParent, aPosition, |
michael@0 | 1383 | getter_AddRefs(txn)); |
michael@0 | 1384 | if (NS_SUCCEEDED(result)) { |
michael@0 | 1385 | result = DoTransaction(txn); |
michael@0 | 1386 | } |
michael@0 | 1387 | |
michael@0 | 1388 | mRangeUpdater.SelAdjInsertNode(aParent, aPosition); |
michael@0 | 1389 | |
michael@0 | 1390 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1391 | mActionListeners[i]->DidInsertNode(aNode, aParent, aPosition, result); |
michael@0 | 1392 | |
michael@0 | 1393 | return result; |
michael@0 | 1394 | } |
michael@0 | 1395 | |
michael@0 | 1396 | |
michael@0 | 1397 | NS_IMETHODIMP |
michael@0 | 1398 | nsEditor::SplitNode(nsIDOMNode * aNode, |
michael@0 | 1399 | int32_t aOffset, |
michael@0 | 1400 | nsIDOMNode **aNewLeftNode) |
michael@0 | 1401 | { |
michael@0 | 1402 | int32_t i; |
michael@0 | 1403 | nsAutoRules beginRulesSniffing(this, EditAction::splitNode, nsIEditor::eNext); |
michael@0 | 1404 | |
michael@0 | 1405 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1406 | mActionListeners[i]->WillSplitNode(aNode, aOffset); |
michael@0 | 1407 | |
michael@0 | 1408 | nsRefPtr<SplitElementTxn> txn; |
michael@0 | 1409 | nsresult result = CreateTxnForSplitNode(aNode, aOffset, getter_AddRefs(txn)); |
michael@0 | 1410 | if (NS_SUCCEEDED(result)) |
michael@0 | 1411 | { |
michael@0 | 1412 | result = DoTransaction(txn); |
michael@0 | 1413 | if (NS_SUCCEEDED(result)) |
michael@0 | 1414 | { |
michael@0 | 1415 | result = txn->GetNewNode(aNewLeftNode); |
michael@0 | 1416 | NS_ASSERTION((NS_SUCCEEDED(result)), "result must succeeded for GetNewNode"); |
michael@0 | 1417 | } |
michael@0 | 1418 | } |
michael@0 | 1419 | |
michael@0 | 1420 | mRangeUpdater.SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); |
michael@0 | 1421 | |
michael@0 | 1422 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1423 | { |
michael@0 | 1424 | nsIDOMNode *ptr = *aNewLeftNode; |
michael@0 | 1425 | mActionListeners[i]->DidSplitNode(aNode, aOffset, ptr, result); |
michael@0 | 1426 | } |
michael@0 | 1427 | |
michael@0 | 1428 | return result; |
michael@0 | 1429 | } |
michael@0 | 1430 | |
michael@0 | 1431 | |
michael@0 | 1432 | nsresult |
michael@0 | 1433 | nsEditor::JoinNodes(nsINode* aNodeToKeep, nsIContent* aNodeToMove) |
michael@0 | 1434 | { |
michael@0 | 1435 | // We don't really need aNodeToMove's parent to be non-null -- we could just |
michael@0 | 1436 | // skip adjusting any ranges in aNodeToMove's parent if there is none. But |
michael@0 | 1437 | // the current implementation requires it. |
michael@0 | 1438 | MOZ_ASSERT(aNodeToKeep && aNodeToMove && aNodeToMove->GetParentNode()); |
michael@0 | 1439 | nsresult res = JoinNodes(aNodeToKeep->AsDOMNode(), aNodeToMove->AsDOMNode(), |
michael@0 | 1440 | aNodeToMove->GetParentNode()->AsDOMNode()); |
michael@0 | 1441 | NS_ASSERTION(NS_SUCCEEDED(res), "JoinNodes failed"); |
michael@0 | 1442 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1443 | return NS_OK; |
michael@0 | 1444 | } |
michael@0 | 1445 | |
michael@0 | 1446 | NS_IMETHODIMP |
michael@0 | 1447 | nsEditor::JoinNodes(nsIDOMNode * aLeftNode, |
michael@0 | 1448 | nsIDOMNode * aRightNode, |
michael@0 | 1449 | nsIDOMNode * aParent) |
michael@0 | 1450 | { |
michael@0 | 1451 | int32_t i; |
michael@0 | 1452 | nsAutoRules beginRulesSniffing(this, EditAction::joinNode, nsIEditor::ePrevious); |
michael@0 | 1453 | |
michael@0 | 1454 | // remember some values; later used for saved selection updating. |
michael@0 | 1455 | // find the offset between the nodes to be joined. |
michael@0 | 1456 | int32_t offset = GetChildOffset(aRightNode, aParent); |
michael@0 | 1457 | // find the number of children of the lefthand node |
michael@0 | 1458 | uint32_t oldLeftNodeLen; |
michael@0 | 1459 | nsresult result = GetLengthOfDOMNode(aLeftNode, oldLeftNodeLen); |
michael@0 | 1460 | NS_ENSURE_SUCCESS(result, result); |
michael@0 | 1461 | |
michael@0 | 1462 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1463 | mActionListeners[i]->WillJoinNodes(aLeftNode, aRightNode, aParent); |
michael@0 | 1464 | |
michael@0 | 1465 | nsRefPtr<JoinElementTxn> txn; |
michael@0 | 1466 | result = CreateTxnForJoinNode(aLeftNode, aRightNode, getter_AddRefs(txn)); |
michael@0 | 1467 | if (NS_SUCCEEDED(result)) { |
michael@0 | 1468 | result = DoTransaction(txn); |
michael@0 | 1469 | } |
michael@0 | 1470 | |
michael@0 | 1471 | mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (int32_t)oldLeftNodeLen); |
michael@0 | 1472 | |
michael@0 | 1473 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 1474 | mActionListeners[i]->DidJoinNodes(aLeftNode, aRightNode, aParent, result); |
michael@0 | 1475 | |
michael@0 | 1476 | return result; |
michael@0 | 1477 | } |
michael@0 | 1478 | |
michael@0 | 1479 | |
michael@0 | 1480 | NS_IMETHODIMP |
michael@0 | 1481 | nsEditor::DeleteNode(nsIDOMNode* aNode) |
michael@0 | 1482 | { |
michael@0 | 1483 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 1484 | NS_ENSURE_STATE(node); |
michael@0 | 1485 | return DeleteNode(node); |
michael@0 | 1486 | } |
michael@0 | 1487 | |
michael@0 | 1488 | nsresult |
michael@0 | 1489 | nsEditor::DeleteNode(nsINode* aNode) |
michael@0 | 1490 | { |
michael@0 | 1491 | nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::ePrevious); |
michael@0 | 1492 | |
michael@0 | 1493 | // save node location for selection updating code. |
michael@0 | 1494 | for (int32_t i = 0; i < mActionListeners.Count(); i++) { |
michael@0 | 1495 | mActionListeners[i]->WillDeleteNode(aNode->AsDOMNode()); |
michael@0 | 1496 | } |
michael@0 | 1497 | |
michael@0 | 1498 | nsRefPtr<DeleteNodeTxn> txn; |
michael@0 | 1499 | nsresult res = CreateTxnForDeleteNode(aNode, getter_AddRefs(txn)); |
michael@0 | 1500 | if (NS_SUCCEEDED(res)) { |
michael@0 | 1501 | res = DoTransaction(txn); |
michael@0 | 1502 | } |
michael@0 | 1503 | |
michael@0 | 1504 | for (int32_t i = 0; i < mActionListeners.Count(); i++) { |
michael@0 | 1505 | mActionListeners[i]->DidDeleteNode(aNode->AsDOMNode(), res); |
michael@0 | 1506 | } |
michael@0 | 1507 | |
michael@0 | 1508 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1509 | return NS_OK; |
michael@0 | 1510 | } |
michael@0 | 1511 | |
michael@0 | 1512 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 1513 | // ReplaceContainer: replace inNode with a new node (outNode) which is contructed |
michael@0 | 1514 | // to be of type aNodeType. Put inNodes children into outNode. |
michael@0 | 1515 | // Callers responsibility to make sure inNode's children can |
michael@0 | 1516 | // go in outNode. |
michael@0 | 1517 | nsresult |
michael@0 | 1518 | nsEditor::ReplaceContainer(nsIDOMNode *inNode, |
michael@0 | 1519 | nsCOMPtr<nsIDOMNode> *outNode, |
michael@0 | 1520 | const nsAString &aNodeType, |
michael@0 | 1521 | const nsAString *aAttribute, |
michael@0 | 1522 | const nsAString *aValue, |
michael@0 | 1523 | bool aCloneAttributes) |
michael@0 | 1524 | { |
michael@0 | 1525 | NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER); |
michael@0 | 1526 | |
michael@0 | 1527 | nsCOMPtr<nsINode> node = do_QueryInterface(inNode); |
michael@0 | 1528 | NS_ENSURE_STATE(node); |
michael@0 | 1529 | |
michael@0 | 1530 | nsCOMPtr<dom::Element> element; |
michael@0 | 1531 | nsresult rv = ReplaceContainer(node, getter_AddRefs(element), aNodeType, |
michael@0 | 1532 | aAttribute, aValue, aCloneAttributes); |
michael@0 | 1533 | *outNode = element ? element->AsDOMNode() : nullptr; |
michael@0 | 1534 | return rv; |
michael@0 | 1535 | } |
michael@0 | 1536 | |
michael@0 | 1537 | nsresult |
michael@0 | 1538 | nsEditor::ReplaceContainer(nsINode* aNode, |
michael@0 | 1539 | dom::Element** outNode, |
michael@0 | 1540 | const nsAString& aNodeType, |
michael@0 | 1541 | const nsAString* aAttribute, |
michael@0 | 1542 | const nsAString* aValue, |
michael@0 | 1543 | bool aCloneAttributes) |
michael@0 | 1544 | { |
michael@0 | 1545 | MOZ_ASSERT(aNode); |
michael@0 | 1546 | MOZ_ASSERT(outNode); |
michael@0 | 1547 | |
michael@0 | 1548 | *outNode = nullptr; |
michael@0 | 1549 | |
michael@0 | 1550 | nsCOMPtr<nsIContent> parent = aNode->GetParent(); |
michael@0 | 1551 | NS_ENSURE_STATE(parent); |
michael@0 | 1552 | |
michael@0 | 1553 | int32_t offset = parent->IndexOf(aNode); |
michael@0 | 1554 | |
michael@0 | 1555 | // create new container |
michael@0 | 1556 | //new call to use instead to get proper HTML element, bug# 39919 |
michael@0 | 1557 | nsresult res = CreateHTMLContent(aNodeType, outNode); |
michael@0 | 1558 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1559 | |
michael@0 | 1560 | nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(*outNode); |
michael@0 | 1561 | |
michael@0 | 1562 | nsIDOMNode* inNode = aNode->AsDOMNode(); |
michael@0 | 1563 | |
michael@0 | 1564 | // set attribute if needed |
michael@0 | 1565 | if (aAttribute && aValue && !aAttribute->IsEmpty()) { |
michael@0 | 1566 | res = elem->SetAttribute(*aAttribute, *aValue); |
michael@0 | 1567 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1568 | } |
michael@0 | 1569 | if (aCloneAttributes) { |
michael@0 | 1570 | res = CloneAttributes(elem, inNode); |
michael@0 | 1571 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1572 | } |
michael@0 | 1573 | |
michael@0 | 1574 | // notify our internal selection state listener |
michael@0 | 1575 | // (Note: A nsAutoSelectionReset object must be created |
michael@0 | 1576 | // before calling this to initialize mRangeUpdater) |
michael@0 | 1577 | nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, inNode, elem); |
michael@0 | 1578 | { |
michael@0 | 1579 | nsAutoTxnsConserveSelection conserveSelection(this); |
michael@0 | 1580 | while (aNode->HasChildren()) { |
michael@0 | 1581 | nsCOMPtr<nsIDOMNode> child = aNode->GetFirstChild()->AsDOMNode(); |
michael@0 | 1582 | |
michael@0 | 1583 | res = DeleteNode(child); |
michael@0 | 1584 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1585 | |
michael@0 | 1586 | res = InsertNode(child, elem, -1); |
michael@0 | 1587 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1588 | } |
michael@0 | 1589 | } |
michael@0 | 1590 | |
michael@0 | 1591 | // insert new container into tree |
michael@0 | 1592 | res = InsertNode(elem, parent->AsDOMNode(), offset); |
michael@0 | 1593 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1594 | |
michael@0 | 1595 | // delete old container |
michael@0 | 1596 | return DeleteNode(inNode); |
michael@0 | 1597 | } |
michael@0 | 1598 | |
michael@0 | 1599 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 1600 | // RemoveContainer: remove inNode, reparenting its children into their |
michael@0 | 1601 | // the parent of inNode |
michael@0 | 1602 | // |
michael@0 | 1603 | nsresult |
michael@0 | 1604 | nsEditor::RemoveContainer(nsIDOMNode* aNode) |
michael@0 | 1605 | { |
michael@0 | 1606 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 1607 | return RemoveContainer(node); |
michael@0 | 1608 | } |
michael@0 | 1609 | |
michael@0 | 1610 | nsresult |
michael@0 | 1611 | nsEditor::RemoveContainer(nsINode* aNode) |
michael@0 | 1612 | { |
michael@0 | 1613 | NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
michael@0 | 1614 | |
michael@0 | 1615 | nsCOMPtr<nsINode> parent = aNode->GetParentNode(); |
michael@0 | 1616 | NS_ENSURE_STATE(parent); |
michael@0 | 1617 | |
michael@0 | 1618 | int32_t offset = parent->IndexOf(aNode); |
michael@0 | 1619 | |
michael@0 | 1620 | // loop through the child nodes of inNode and promote them |
michael@0 | 1621 | // into inNode's parent. |
michael@0 | 1622 | uint32_t nodeOrigLen = aNode->GetChildCount(); |
michael@0 | 1623 | |
michael@0 | 1624 | // notify our internal selection state listener |
michael@0 | 1625 | nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent, offset, nodeOrigLen); |
michael@0 | 1626 | |
michael@0 | 1627 | while (aNode->HasChildren()) { |
michael@0 | 1628 | nsCOMPtr<nsIContent> child = aNode->GetLastChild(); |
michael@0 | 1629 | nsresult rv = DeleteNode(child->AsDOMNode()); |
michael@0 | 1630 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1631 | |
michael@0 | 1632 | rv = InsertNode(child->AsDOMNode(), parent->AsDOMNode(), offset); |
michael@0 | 1633 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1634 | } |
michael@0 | 1635 | |
michael@0 | 1636 | return DeleteNode(aNode->AsDOMNode()); |
michael@0 | 1637 | } |
michael@0 | 1638 | |
michael@0 | 1639 | |
michael@0 | 1640 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 1641 | // InsertContainerAbove: insert a new parent for inNode, returned in outNode, |
michael@0 | 1642 | // which is contructed to be of type aNodeType. outNode becomes |
michael@0 | 1643 | // a child of inNode's earlier parent. |
michael@0 | 1644 | // Callers responsibility to make sure inNode's can be child |
michael@0 | 1645 | // of outNode, and outNode can be child of old parent. |
michael@0 | 1646 | nsresult |
michael@0 | 1647 | nsEditor::InsertContainerAbove( nsIDOMNode *inNode, |
michael@0 | 1648 | nsCOMPtr<nsIDOMNode> *outNode, |
michael@0 | 1649 | const nsAString &aNodeType, |
michael@0 | 1650 | const nsAString *aAttribute, |
michael@0 | 1651 | const nsAString *aValue) |
michael@0 | 1652 | { |
michael@0 | 1653 | NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER); |
michael@0 | 1654 | |
michael@0 | 1655 | nsCOMPtr<nsIContent> node = do_QueryInterface(inNode); |
michael@0 | 1656 | NS_ENSURE_STATE(node); |
michael@0 | 1657 | |
michael@0 | 1658 | nsCOMPtr<dom::Element> element; |
michael@0 | 1659 | nsresult rv = InsertContainerAbove(node, getter_AddRefs(element), aNodeType, |
michael@0 | 1660 | aAttribute, aValue); |
michael@0 | 1661 | *outNode = element ? element->AsDOMNode() : nullptr; |
michael@0 | 1662 | return rv; |
michael@0 | 1663 | } |
michael@0 | 1664 | |
michael@0 | 1665 | nsresult |
michael@0 | 1666 | nsEditor::InsertContainerAbove(nsIContent* aNode, |
michael@0 | 1667 | dom::Element** aOutNode, |
michael@0 | 1668 | const nsAString& aNodeType, |
michael@0 | 1669 | const nsAString* aAttribute, |
michael@0 | 1670 | const nsAString* aValue) |
michael@0 | 1671 | { |
michael@0 | 1672 | MOZ_ASSERT(aNode); |
michael@0 | 1673 | |
michael@0 | 1674 | nsCOMPtr<nsIContent> parent = aNode->GetParent(); |
michael@0 | 1675 | NS_ENSURE_STATE(parent); |
michael@0 | 1676 | int32_t offset = parent->IndexOf(aNode); |
michael@0 | 1677 | |
michael@0 | 1678 | // create new container |
michael@0 | 1679 | nsCOMPtr<dom::Element> newContent; |
michael@0 | 1680 | |
michael@0 | 1681 | //new call to use instead to get proper HTML element, bug# 39919 |
michael@0 | 1682 | nsresult res = CreateHTMLContent(aNodeType, getter_AddRefs(newContent)); |
michael@0 | 1683 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1684 | |
michael@0 | 1685 | // set attribute if needed |
michael@0 | 1686 | if (aAttribute && aValue && !aAttribute->IsEmpty()) { |
michael@0 | 1687 | nsIDOMNode* elem = newContent->AsDOMNode(); |
michael@0 | 1688 | res = static_cast<nsIDOMElement*>(elem)->SetAttribute(*aAttribute, *aValue); |
michael@0 | 1689 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1690 | } |
michael@0 | 1691 | |
michael@0 | 1692 | // notify our internal selection state listener |
michael@0 | 1693 | nsAutoInsertContainerSelNotify selNotify(mRangeUpdater); |
michael@0 | 1694 | |
michael@0 | 1695 | // put inNode in new parent, outNode |
michael@0 | 1696 | res = DeleteNode(aNode->AsDOMNode()); |
michael@0 | 1697 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1698 | |
michael@0 | 1699 | { |
michael@0 | 1700 | nsAutoTxnsConserveSelection conserveSelection(this); |
michael@0 | 1701 | res = InsertNode(aNode->AsDOMNode(), newContent->AsDOMNode(), 0); |
michael@0 | 1702 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1703 | } |
michael@0 | 1704 | |
michael@0 | 1705 | // put new parent in doc |
michael@0 | 1706 | res = InsertNode(newContent->AsDOMNode(), parent->AsDOMNode(), offset); |
michael@0 | 1707 | newContent.forget(aOutNode); |
michael@0 | 1708 | return res; |
michael@0 | 1709 | } |
michael@0 | 1710 | |
michael@0 | 1711 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 1712 | // MoveNode: move aNode to {aParent,aOffset} |
michael@0 | 1713 | nsresult |
michael@0 | 1714 | nsEditor::MoveNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aOffset) |
michael@0 | 1715 | { |
michael@0 | 1716 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 1717 | NS_ENSURE_STATE(node); |
michael@0 | 1718 | |
michael@0 | 1719 | nsCOMPtr<nsINode> parent = do_QueryInterface(aParent); |
michael@0 | 1720 | NS_ENSURE_STATE(parent); |
michael@0 | 1721 | |
michael@0 | 1722 | return MoveNode(node, parent, aOffset); |
michael@0 | 1723 | } |
michael@0 | 1724 | |
michael@0 | 1725 | nsresult |
michael@0 | 1726 | nsEditor::MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset) |
michael@0 | 1727 | { |
michael@0 | 1728 | MOZ_ASSERT(aNode); |
michael@0 | 1729 | MOZ_ASSERT(aParent); |
michael@0 | 1730 | MOZ_ASSERT(aOffset == -1 || |
michael@0 | 1731 | (0 <= aOffset && SafeCast<uint32_t>(aOffset) <= aParent->Length())); |
michael@0 | 1732 | |
michael@0 | 1733 | int32_t oldOffset; |
michael@0 | 1734 | nsCOMPtr<nsINode> oldParent = GetNodeLocation(aNode, &oldOffset); |
michael@0 | 1735 | |
michael@0 | 1736 | if (aOffset == -1) { |
michael@0 | 1737 | // Magic value meaning "move to end of aParent". |
michael@0 | 1738 | aOffset = SafeCast<int32_t>(aParent->Length()); |
michael@0 | 1739 | } |
michael@0 | 1740 | |
michael@0 | 1741 | // Don't do anything if it's already in right place. |
michael@0 | 1742 | if (aParent == oldParent && aOffset == oldOffset) { |
michael@0 | 1743 | return NS_OK; |
michael@0 | 1744 | } |
michael@0 | 1745 | |
michael@0 | 1746 | // Notify our internal selection state listener. |
michael@0 | 1747 | nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset, |
michael@0 | 1748 | aParent, aOffset); |
michael@0 | 1749 | |
michael@0 | 1750 | // Need to adjust aOffset if we are moving aNode further along in its current |
michael@0 | 1751 | // parent. |
michael@0 | 1752 | if (aParent == oldParent && oldOffset < aOffset) { |
michael@0 | 1753 | // This is because when we delete aNode, it will make the offsets after it |
michael@0 | 1754 | // off by one. |
michael@0 | 1755 | aOffset--; |
michael@0 | 1756 | } |
michael@0 | 1757 | |
michael@0 | 1758 | // Hold a reference so aNode doesn't go away when we remove it (bug 772282). |
michael@0 | 1759 | nsCOMPtr<nsINode> kungFuDeathGrip = aNode; |
michael@0 | 1760 | |
michael@0 | 1761 | nsresult rv = DeleteNode(aNode); |
michael@0 | 1762 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1763 | |
michael@0 | 1764 | return InsertNode(aNode->AsDOMNode(), aParent->AsDOMNode(), aOffset); |
michael@0 | 1765 | } |
michael@0 | 1766 | |
michael@0 | 1767 | |
michael@0 | 1768 | NS_IMETHODIMP |
michael@0 | 1769 | nsEditor::AddEditorObserver(nsIEditorObserver *aObserver) |
michael@0 | 1770 | { |
michael@0 | 1771 | // we don't keep ownership of the observers. They must |
michael@0 | 1772 | // remove themselves as observers before they are destroyed. |
michael@0 | 1773 | |
michael@0 | 1774 | NS_ENSURE_TRUE(aObserver, NS_ERROR_NULL_POINTER); |
michael@0 | 1775 | |
michael@0 | 1776 | // Make sure the listener isn't already on the list |
michael@0 | 1777 | if (mEditorObservers.IndexOf(aObserver) == -1) |
michael@0 | 1778 | { |
michael@0 | 1779 | if (!mEditorObservers.AppendObject(aObserver)) |
michael@0 | 1780 | return NS_ERROR_FAILURE; |
michael@0 | 1781 | } |
michael@0 | 1782 | |
michael@0 | 1783 | return NS_OK; |
michael@0 | 1784 | } |
michael@0 | 1785 | |
michael@0 | 1786 | |
michael@0 | 1787 | NS_IMETHODIMP |
michael@0 | 1788 | nsEditor::RemoveEditorObserver(nsIEditorObserver *aObserver) |
michael@0 | 1789 | { |
michael@0 | 1790 | NS_ENSURE_TRUE(aObserver, NS_ERROR_FAILURE); |
michael@0 | 1791 | |
michael@0 | 1792 | if (!mEditorObservers.RemoveObject(aObserver)) |
michael@0 | 1793 | return NS_ERROR_FAILURE; |
michael@0 | 1794 | |
michael@0 | 1795 | return NS_OK; |
michael@0 | 1796 | } |
michael@0 | 1797 | |
michael@0 | 1798 | class EditorInputEventDispatcher : public nsRunnable |
michael@0 | 1799 | { |
michael@0 | 1800 | public: |
michael@0 | 1801 | EditorInputEventDispatcher(nsEditor* aEditor, |
michael@0 | 1802 | nsIContent* aTarget, |
michael@0 | 1803 | bool aIsComposing) |
michael@0 | 1804 | : mEditor(aEditor) |
michael@0 | 1805 | , mTarget(aTarget) |
michael@0 | 1806 | , mIsComposing(aIsComposing) |
michael@0 | 1807 | { |
michael@0 | 1808 | } |
michael@0 | 1809 | |
michael@0 | 1810 | NS_IMETHOD Run() |
michael@0 | 1811 | { |
michael@0 | 1812 | // Note that we don't need to check mDispatchInputEvent here. We need |
michael@0 | 1813 | // to check it only when the editor requests to dispatch the input event. |
michael@0 | 1814 | |
michael@0 | 1815 | if (!mTarget->IsInDoc()) { |
michael@0 | 1816 | return NS_OK; |
michael@0 | 1817 | } |
michael@0 | 1818 | |
michael@0 | 1819 | nsCOMPtr<nsIPresShell> ps = mEditor->GetPresShell(); |
michael@0 | 1820 | if (!ps) { |
michael@0 | 1821 | return NS_OK; |
michael@0 | 1822 | } |
michael@0 | 1823 | |
michael@0 | 1824 | nsCOMPtr<nsIWidget> widget = mEditor->GetWidget(); |
michael@0 | 1825 | if (!widget) { |
michael@0 | 1826 | return NS_OK; |
michael@0 | 1827 | } |
michael@0 | 1828 | |
michael@0 | 1829 | // Even if the change is caused by untrusted event, we need to dispatch |
michael@0 | 1830 | // trusted input event since it's a fact. |
michael@0 | 1831 | InternalEditorInputEvent inputEvent(true, NS_EDITOR_INPUT, widget); |
michael@0 | 1832 | inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000); |
michael@0 | 1833 | inputEvent.mIsComposing = mIsComposing; |
michael@0 | 1834 | nsEventStatus status = nsEventStatus_eIgnore; |
michael@0 | 1835 | nsresult rv = |
michael@0 | 1836 | ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status); |
michael@0 | 1837 | NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error |
michael@0 | 1838 | return NS_OK; |
michael@0 | 1839 | } |
michael@0 | 1840 | |
michael@0 | 1841 | private: |
michael@0 | 1842 | nsRefPtr<nsEditor> mEditor; |
michael@0 | 1843 | nsCOMPtr<nsIContent> mTarget; |
michael@0 | 1844 | bool mIsComposing; |
michael@0 | 1845 | }; |
michael@0 | 1846 | |
michael@0 | 1847 | void nsEditor::NotifyEditorObservers(void) |
michael@0 | 1848 | { |
michael@0 | 1849 | for (int32_t i = 0; i < mEditorObservers.Count(); i++) { |
michael@0 | 1850 | mEditorObservers[i]->EditAction(); |
michael@0 | 1851 | } |
michael@0 | 1852 | |
michael@0 | 1853 | if (!mDispatchInputEvent) { |
michael@0 | 1854 | return; |
michael@0 | 1855 | } |
michael@0 | 1856 | |
michael@0 | 1857 | FireInputEvent(); |
michael@0 | 1858 | } |
michael@0 | 1859 | |
michael@0 | 1860 | void |
michael@0 | 1861 | nsEditor::FireInputEvent() |
michael@0 | 1862 | { |
michael@0 | 1863 | // We don't need to dispatch multiple input events if there is a pending |
michael@0 | 1864 | // input event. However, it may have different event target. If we resolved |
michael@0 | 1865 | // this issue, we need to manage the pending events in an array. But it's |
michael@0 | 1866 | // overwork. We don't need to do it for the very rare case. |
michael@0 | 1867 | |
michael@0 | 1868 | nsCOMPtr<nsIContent> target = GetInputEventTargetContent(); |
michael@0 | 1869 | NS_ENSURE_TRUE_VOID(target); |
michael@0 | 1870 | |
michael@0 | 1871 | // NOTE: Don't refer IsIMEComposing() because it returns false even before |
michael@0 | 1872 | // compositionend. However, DOM Level 3 Events defines it should be |
michael@0 | 1873 | // true after compositionstart and before compositionend. |
michael@0 | 1874 | nsContentUtils::AddScriptRunner( |
michael@0 | 1875 | new EditorInputEventDispatcher(this, target, !!GetComposition())); |
michael@0 | 1876 | } |
michael@0 | 1877 | |
michael@0 | 1878 | NS_IMETHODIMP |
michael@0 | 1879 | nsEditor::AddEditActionListener(nsIEditActionListener *aListener) |
michael@0 | 1880 | { |
michael@0 | 1881 | NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); |
michael@0 | 1882 | |
michael@0 | 1883 | // Make sure the listener isn't already on the list |
michael@0 | 1884 | if (mActionListeners.IndexOf(aListener) == -1) |
michael@0 | 1885 | { |
michael@0 | 1886 | if (!mActionListeners.AppendObject(aListener)) |
michael@0 | 1887 | return NS_ERROR_FAILURE; |
michael@0 | 1888 | } |
michael@0 | 1889 | |
michael@0 | 1890 | return NS_OK; |
michael@0 | 1891 | } |
michael@0 | 1892 | |
michael@0 | 1893 | |
michael@0 | 1894 | NS_IMETHODIMP |
michael@0 | 1895 | nsEditor::RemoveEditActionListener(nsIEditActionListener *aListener) |
michael@0 | 1896 | { |
michael@0 | 1897 | NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE); |
michael@0 | 1898 | |
michael@0 | 1899 | if (!mActionListeners.RemoveObject(aListener)) |
michael@0 | 1900 | return NS_ERROR_FAILURE; |
michael@0 | 1901 | |
michael@0 | 1902 | return NS_OK; |
michael@0 | 1903 | } |
michael@0 | 1904 | |
michael@0 | 1905 | |
michael@0 | 1906 | NS_IMETHODIMP |
michael@0 | 1907 | nsEditor::AddDocumentStateListener(nsIDocumentStateListener *aListener) |
michael@0 | 1908 | { |
michael@0 | 1909 | NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); |
michael@0 | 1910 | |
michael@0 | 1911 | if (mDocStateListeners.IndexOf(aListener) == -1) |
michael@0 | 1912 | { |
michael@0 | 1913 | if (!mDocStateListeners.AppendObject(aListener)) |
michael@0 | 1914 | return NS_ERROR_FAILURE; |
michael@0 | 1915 | } |
michael@0 | 1916 | |
michael@0 | 1917 | return NS_OK; |
michael@0 | 1918 | } |
michael@0 | 1919 | |
michael@0 | 1920 | |
michael@0 | 1921 | NS_IMETHODIMP |
michael@0 | 1922 | nsEditor::RemoveDocumentStateListener(nsIDocumentStateListener *aListener) |
michael@0 | 1923 | { |
michael@0 | 1924 | NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); |
michael@0 | 1925 | |
michael@0 | 1926 | if (!mDocStateListeners.RemoveObject(aListener)) |
michael@0 | 1927 | return NS_ERROR_FAILURE; |
michael@0 | 1928 | |
michael@0 | 1929 | return NS_OK; |
michael@0 | 1930 | } |
michael@0 | 1931 | |
michael@0 | 1932 | |
michael@0 | 1933 | NS_IMETHODIMP nsEditor::OutputToString(const nsAString& aFormatType, |
michael@0 | 1934 | uint32_t aFlags, |
michael@0 | 1935 | nsAString& aOutputString) |
michael@0 | 1936 | { |
michael@0 | 1937 | // these should be implemented by derived classes. |
michael@0 | 1938 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1939 | } |
michael@0 | 1940 | |
michael@0 | 1941 | NS_IMETHODIMP |
michael@0 | 1942 | nsEditor::OutputToStream(nsIOutputStream* aOutputStream, |
michael@0 | 1943 | const nsAString& aFormatType, |
michael@0 | 1944 | const nsACString& aCharsetOverride, |
michael@0 | 1945 | uint32_t aFlags) |
michael@0 | 1946 | { |
michael@0 | 1947 | // these should be implemented by derived classes. |
michael@0 | 1948 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 1949 | } |
michael@0 | 1950 | |
michael@0 | 1951 | NS_IMETHODIMP |
michael@0 | 1952 | nsEditor::DumpContentTree() |
michael@0 | 1953 | { |
michael@0 | 1954 | #ifdef DEBUG |
michael@0 | 1955 | if (mRootElement) { |
michael@0 | 1956 | mRootElement->List(stdout); |
michael@0 | 1957 | } |
michael@0 | 1958 | #endif |
michael@0 | 1959 | return NS_OK; |
michael@0 | 1960 | } |
michael@0 | 1961 | |
michael@0 | 1962 | |
michael@0 | 1963 | NS_IMETHODIMP |
michael@0 | 1964 | nsEditor::DebugDumpContent() |
michael@0 | 1965 | { |
michael@0 | 1966 | #ifdef DEBUG |
michael@0 | 1967 | nsCOMPtr<nsIDOMHTMLDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 1968 | NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1969 | |
michael@0 | 1970 | nsCOMPtr<nsIDOMHTMLElement>bodyElem; |
michael@0 | 1971 | doc->GetBody(getter_AddRefs(bodyElem)); |
michael@0 | 1972 | nsCOMPtr<nsIContent> content = do_QueryInterface(bodyElem); |
michael@0 | 1973 | if (content) |
michael@0 | 1974 | content->List(); |
michael@0 | 1975 | #endif |
michael@0 | 1976 | return NS_OK; |
michael@0 | 1977 | } |
michael@0 | 1978 | |
michael@0 | 1979 | |
michael@0 | 1980 | NS_IMETHODIMP |
michael@0 | 1981 | nsEditor::DebugUnitTests(int32_t *outNumTests, int32_t *outNumTestsFailed) |
michael@0 | 1982 | { |
michael@0 | 1983 | #ifdef DEBUG |
michael@0 | 1984 | NS_NOTREACHED("This should never get called. Overridden by subclasses"); |
michael@0 | 1985 | #endif |
michael@0 | 1986 | return NS_OK; |
michael@0 | 1987 | } |
michael@0 | 1988 | |
michael@0 | 1989 | |
michael@0 | 1990 | bool |
michael@0 | 1991 | nsEditor::ArePreservingSelection() |
michael@0 | 1992 | { |
michael@0 | 1993 | return !(mSavedSel.IsEmpty()); |
michael@0 | 1994 | } |
michael@0 | 1995 | |
michael@0 | 1996 | void |
michael@0 | 1997 | nsEditor::PreserveSelectionAcrossActions(Selection* aSel) |
michael@0 | 1998 | { |
michael@0 | 1999 | mSavedSel.SaveSelection(aSel); |
michael@0 | 2000 | mRangeUpdater.RegisterSelectionState(mSavedSel); |
michael@0 | 2001 | } |
michael@0 | 2002 | |
michael@0 | 2003 | nsresult |
michael@0 | 2004 | nsEditor::RestorePreservedSelection(nsISelection *aSel) |
michael@0 | 2005 | { |
michael@0 | 2006 | if (mSavedSel.IsEmpty()) return NS_ERROR_FAILURE; |
michael@0 | 2007 | mSavedSel.RestoreSelection(aSel); |
michael@0 | 2008 | StopPreservingSelection(); |
michael@0 | 2009 | return NS_OK; |
michael@0 | 2010 | } |
michael@0 | 2011 | |
michael@0 | 2012 | void |
michael@0 | 2013 | nsEditor::StopPreservingSelection() |
michael@0 | 2014 | { |
michael@0 | 2015 | mRangeUpdater.DropSelectionState(mSavedSel); |
michael@0 | 2016 | mSavedSel.MakeEmpty(); |
michael@0 | 2017 | } |
michael@0 | 2018 | |
michael@0 | 2019 | void |
michael@0 | 2020 | nsEditor::EnsureComposition(mozilla::WidgetGUIEvent* aEvent) |
michael@0 | 2021 | { |
michael@0 | 2022 | if (mComposition) { |
michael@0 | 2023 | return; |
michael@0 | 2024 | } |
michael@0 | 2025 | // The compositionstart event must cause creating new TextComposition |
michael@0 | 2026 | // instance at being dispatched by IMEStateManager. |
michael@0 | 2027 | mComposition = IMEStateManager::GetTextCompositionFor(aEvent); |
michael@0 | 2028 | if (!mComposition) { |
michael@0 | 2029 | MOZ_CRASH("IMEStateManager doesn't return proper composition"); |
michael@0 | 2030 | } |
michael@0 | 2031 | mComposition->StartHandlingComposition(this); |
michael@0 | 2032 | } |
michael@0 | 2033 | |
michael@0 | 2034 | nsresult |
michael@0 | 2035 | nsEditor::BeginIMEComposition(WidgetCompositionEvent* aCompositionEvent) |
michael@0 | 2036 | { |
michael@0 | 2037 | MOZ_ASSERT(!mComposition, "There is composition already"); |
michael@0 | 2038 | EnsureComposition(aCompositionEvent); |
michael@0 | 2039 | if (mPhonetic) { |
michael@0 | 2040 | mPhonetic->Truncate(0); |
michael@0 | 2041 | } |
michael@0 | 2042 | return NS_OK; |
michael@0 | 2043 | } |
michael@0 | 2044 | |
michael@0 | 2045 | void |
michael@0 | 2046 | nsEditor::EndIMEComposition() |
michael@0 | 2047 | { |
michael@0 | 2048 | NS_ENSURE_TRUE_VOID(mComposition); // nothing to do |
michael@0 | 2049 | |
michael@0 | 2050 | // commit the IME transaction..we can get at it via the transaction mgr. |
michael@0 | 2051 | // Note that this means IME won't work without an undo stack! |
michael@0 | 2052 | if (mTxnMgr) { |
michael@0 | 2053 | nsCOMPtr<nsITransaction> txn = mTxnMgr->PeekUndoStack(); |
michael@0 | 2054 | nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn); |
michael@0 | 2055 | if (plcTxn) { |
michael@0 | 2056 | DebugOnly<nsresult> rv = plcTxn->Commit(); |
michael@0 | 2057 | NS_ASSERTION(NS_SUCCEEDED(rv), |
michael@0 | 2058 | "nsIAbsorbingTransaction::Commit() failed"); |
michael@0 | 2059 | } |
michael@0 | 2060 | } |
michael@0 | 2061 | |
michael@0 | 2062 | /* reset the data we need to construct a transaction */ |
michael@0 | 2063 | mIMETextNode = nullptr; |
michael@0 | 2064 | mIMETextOffset = 0; |
michael@0 | 2065 | mComposition->EndHandlingComposition(this); |
michael@0 | 2066 | mComposition = nullptr; |
michael@0 | 2067 | |
michael@0 | 2068 | // notify editor observers of action |
michael@0 | 2069 | NotifyEditorObservers(); |
michael@0 | 2070 | } |
michael@0 | 2071 | |
michael@0 | 2072 | |
michael@0 | 2073 | NS_IMETHODIMP |
michael@0 | 2074 | nsEditor::GetPhonetic(nsAString& aPhonetic) |
michael@0 | 2075 | { |
michael@0 | 2076 | if (mPhonetic) |
michael@0 | 2077 | aPhonetic = *mPhonetic; |
michael@0 | 2078 | else |
michael@0 | 2079 | aPhonetic.Truncate(0); |
michael@0 | 2080 | |
michael@0 | 2081 | return NS_OK; |
michael@0 | 2082 | } |
michael@0 | 2083 | |
michael@0 | 2084 | NS_IMETHODIMP |
michael@0 | 2085 | nsEditor::ForceCompositionEnd() |
michael@0 | 2086 | { |
michael@0 | 2087 | nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
michael@0 | 2088 | if (!ps) { |
michael@0 | 2089 | return NS_ERROR_NOT_AVAILABLE; |
michael@0 | 2090 | } |
michael@0 | 2091 | nsPresContext* pc = ps->GetPresContext(); |
michael@0 | 2092 | if (!pc) { |
michael@0 | 2093 | return NS_ERROR_NOT_AVAILABLE; |
michael@0 | 2094 | } |
michael@0 | 2095 | |
michael@0 | 2096 | if (!mComposition) { |
michael@0 | 2097 | // XXXmnakano see bug 558976, ResetInputState() has two meaning which are |
michael@0 | 2098 | // "commit the composition" and "cursor is moved". This method name is |
michael@0 | 2099 | // "ForceCompositionEnd", so, ResetInputState() should be used only for the |
michael@0 | 2100 | // former here. However, ResetInputState() is also used for the latter here |
michael@0 | 2101 | // because even if we don't have composition, we call ResetInputState() on |
michael@0 | 2102 | // Linux. Currently, nsGtkIMModule can know the timing of the cursor move, |
michael@0 | 2103 | // so, the latter meaning should be gone. |
michael@0 | 2104 | // XXX This may commit a composition in another editor. |
michael@0 | 2105 | return IMEStateManager::NotifyIME(NOTIFY_IME_OF_CURSOR_POS_CHANGED, pc); |
michael@0 | 2106 | } |
michael@0 | 2107 | |
michael@0 | 2108 | return IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, pc); |
michael@0 | 2109 | } |
michael@0 | 2110 | |
michael@0 | 2111 | NS_IMETHODIMP |
michael@0 | 2112 | nsEditor::GetPreferredIMEState(IMEState *aState) |
michael@0 | 2113 | { |
michael@0 | 2114 | NS_ENSURE_ARG_POINTER(aState); |
michael@0 | 2115 | aState->mEnabled = IMEState::ENABLED; |
michael@0 | 2116 | aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE; |
michael@0 | 2117 | |
michael@0 | 2118 | if (IsReadonly() || IsDisabled()) { |
michael@0 | 2119 | aState->mEnabled = IMEState::DISABLED; |
michael@0 | 2120 | return NS_OK; |
michael@0 | 2121 | } |
michael@0 | 2122 | |
michael@0 | 2123 | nsCOMPtr<nsIContent> content = GetRoot(); |
michael@0 | 2124 | NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); |
michael@0 | 2125 | |
michael@0 | 2126 | nsIFrame* frame = content->GetPrimaryFrame(); |
michael@0 | 2127 | NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); |
michael@0 | 2128 | |
michael@0 | 2129 | switch (frame->StyleUIReset()->mIMEMode) { |
michael@0 | 2130 | case NS_STYLE_IME_MODE_AUTO: |
michael@0 | 2131 | if (IsPasswordEditor()) |
michael@0 | 2132 | aState->mEnabled = IMEState::PASSWORD; |
michael@0 | 2133 | break; |
michael@0 | 2134 | case NS_STYLE_IME_MODE_DISABLED: |
michael@0 | 2135 | // we should use password state for |ime-mode: disabled;|. |
michael@0 | 2136 | aState->mEnabled = IMEState::PASSWORD; |
michael@0 | 2137 | break; |
michael@0 | 2138 | case NS_STYLE_IME_MODE_ACTIVE: |
michael@0 | 2139 | aState->mOpen = IMEState::OPEN; |
michael@0 | 2140 | break; |
michael@0 | 2141 | case NS_STYLE_IME_MODE_INACTIVE: |
michael@0 | 2142 | aState->mOpen = IMEState::CLOSED; |
michael@0 | 2143 | break; |
michael@0 | 2144 | } |
michael@0 | 2145 | |
michael@0 | 2146 | return NS_OK; |
michael@0 | 2147 | } |
michael@0 | 2148 | |
michael@0 | 2149 | NS_IMETHODIMP |
michael@0 | 2150 | nsEditor::GetComposing(bool* aResult) |
michael@0 | 2151 | { |
michael@0 | 2152 | NS_ENSURE_ARG_POINTER(aResult); |
michael@0 | 2153 | *aResult = IsIMEComposing(); |
michael@0 | 2154 | return NS_OK; |
michael@0 | 2155 | } |
michael@0 | 2156 | |
michael@0 | 2157 | |
michael@0 | 2158 | /* Non-interface, public methods */ |
michael@0 | 2159 | |
michael@0 | 2160 | NS_IMETHODIMP |
michael@0 | 2161 | nsEditor::GetRootElement(nsIDOMElement **aRootElement) |
michael@0 | 2162 | { |
michael@0 | 2163 | NS_ENSURE_ARG_POINTER(aRootElement); |
michael@0 | 2164 | NS_ENSURE_TRUE(mRootElement, NS_ERROR_NOT_AVAILABLE); |
michael@0 | 2165 | nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mRootElement); |
michael@0 | 2166 | rootElement.forget(aRootElement); |
michael@0 | 2167 | return NS_OK; |
michael@0 | 2168 | } |
michael@0 | 2169 | |
michael@0 | 2170 | |
michael@0 | 2171 | /** All editor operations which alter the doc should be prefaced |
michael@0 | 2172 | * with a call to StartOperation, naming the action and direction */ |
michael@0 | 2173 | NS_IMETHODIMP |
michael@0 | 2174 | nsEditor::StartOperation(EditAction opID, nsIEditor::EDirection aDirection) |
michael@0 | 2175 | { |
michael@0 | 2176 | mAction = opID; |
michael@0 | 2177 | mDirection = aDirection; |
michael@0 | 2178 | return NS_OK; |
michael@0 | 2179 | } |
michael@0 | 2180 | |
michael@0 | 2181 | |
michael@0 | 2182 | /** All editor operations which alter the doc should be followed |
michael@0 | 2183 | * with a call to EndOperation */ |
michael@0 | 2184 | NS_IMETHODIMP |
michael@0 | 2185 | nsEditor::EndOperation() |
michael@0 | 2186 | { |
michael@0 | 2187 | mAction = EditAction::none; |
michael@0 | 2188 | mDirection = eNone; |
michael@0 | 2189 | return NS_OK; |
michael@0 | 2190 | } |
michael@0 | 2191 | |
michael@0 | 2192 | NS_IMETHODIMP |
michael@0 | 2193 | nsEditor::CloneAttribute(const nsAString & aAttribute, |
michael@0 | 2194 | nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode) |
michael@0 | 2195 | { |
michael@0 | 2196 | NS_ENSURE_TRUE(aDestNode && aSourceNode, NS_ERROR_NULL_POINTER); |
michael@0 | 2197 | |
michael@0 | 2198 | nsCOMPtr<nsIDOMElement> destElement = do_QueryInterface(aDestNode); |
michael@0 | 2199 | nsCOMPtr<nsIDOMElement> sourceElement = do_QueryInterface(aSourceNode); |
michael@0 | 2200 | NS_ENSURE_TRUE(destElement && sourceElement, NS_ERROR_NO_INTERFACE); |
michael@0 | 2201 | |
michael@0 | 2202 | nsAutoString attrValue; |
michael@0 | 2203 | bool isAttrSet; |
michael@0 | 2204 | nsresult rv = GetAttributeValue(sourceElement, |
michael@0 | 2205 | aAttribute, |
michael@0 | 2206 | attrValue, |
michael@0 | 2207 | &isAttrSet); |
michael@0 | 2208 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2209 | if (isAttrSet) |
michael@0 | 2210 | rv = SetAttribute(destElement, aAttribute, attrValue); |
michael@0 | 2211 | else |
michael@0 | 2212 | rv = RemoveAttribute(destElement, aAttribute); |
michael@0 | 2213 | |
michael@0 | 2214 | return rv; |
michael@0 | 2215 | } |
michael@0 | 2216 | |
michael@0 | 2217 | // Objects must be DOM elements |
michael@0 | 2218 | NS_IMETHODIMP |
michael@0 | 2219 | nsEditor::CloneAttributes(nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode) |
michael@0 | 2220 | { |
michael@0 | 2221 | NS_ENSURE_TRUE(aDestNode && aSourceNode, NS_ERROR_NULL_POINTER); |
michael@0 | 2222 | |
michael@0 | 2223 | nsCOMPtr<nsIDOMElement> destElement = do_QueryInterface(aDestNode); |
michael@0 | 2224 | nsCOMPtr<nsIDOMElement> sourceElement = do_QueryInterface(aSourceNode); |
michael@0 | 2225 | NS_ENSURE_TRUE(destElement && sourceElement, NS_ERROR_NO_INTERFACE); |
michael@0 | 2226 | |
michael@0 | 2227 | nsCOMPtr<nsIDOMMozNamedAttrMap> sourceAttributes; |
michael@0 | 2228 | sourceElement->GetAttributes(getter_AddRefs(sourceAttributes)); |
michael@0 | 2229 | nsCOMPtr<nsIDOMMozNamedAttrMap> destAttributes; |
michael@0 | 2230 | destElement->GetAttributes(getter_AddRefs(destAttributes)); |
michael@0 | 2231 | NS_ENSURE_TRUE(sourceAttributes && destAttributes, NS_ERROR_FAILURE); |
michael@0 | 2232 | |
michael@0 | 2233 | nsAutoEditBatch beginBatching(this); |
michael@0 | 2234 | |
michael@0 | 2235 | // Use transaction system for undo only if destination |
michael@0 | 2236 | // is already in the document |
michael@0 | 2237 | nsCOMPtr<nsIDOMNode> p = aDestNode; |
michael@0 | 2238 | nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(GetRoot()); |
michael@0 | 2239 | NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER); |
michael@0 | 2240 | bool destInBody = true; |
michael@0 | 2241 | while (p && p != rootNode) |
michael@0 | 2242 | { |
michael@0 | 2243 | nsCOMPtr<nsIDOMNode> tmp; |
michael@0 | 2244 | if (NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) |
michael@0 | 2245 | { |
michael@0 | 2246 | destInBody = false; |
michael@0 | 2247 | break; |
michael@0 | 2248 | } |
michael@0 | 2249 | p = tmp; |
michael@0 | 2250 | } |
michael@0 | 2251 | |
michael@0 | 2252 | uint32_t sourceCount; |
michael@0 | 2253 | sourceAttributes->GetLength(&sourceCount); |
michael@0 | 2254 | uint32_t destCount; |
michael@0 | 2255 | destAttributes->GetLength(&destCount); |
michael@0 | 2256 | nsCOMPtr<nsIDOMAttr> attr; |
michael@0 | 2257 | |
michael@0 | 2258 | // Clear existing attributes |
michael@0 | 2259 | for (uint32_t i = 0; i < destCount; i++) { |
michael@0 | 2260 | // always remove item number 0 (first item in list) |
michael@0 | 2261 | if (NS_SUCCEEDED(destAttributes->Item(0, getter_AddRefs(attr))) && attr) { |
michael@0 | 2262 | nsString str; |
michael@0 | 2263 | if (NS_SUCCEEDED(attr->GetName(str))) { |
michael@0 | 2264 | if (destInBody) { |
michael@0 | 2265 | RemoveAttribute(destElement, str); |
michael@0 | 2266 | } else { |
michael@0 | 2267 | destElement->RemoveAttribute(str); |
michael@0 | 2268 | } |
michael@0 | 2269 | } |
michael@0 | 2270 | } |
michael@0 | 2271 | } |
michael@0 | 2272 | |
michael@0 | 2273 | nsresult result = NS_OK; |
michael@0 | 2274 | |
michael@0 | 2275 | // Set just the attributes that the source element has |
michael@0 | 2276 | for (uint32_t i = 0; i < sourceCount; i++) |
michael@0 | 2277 | { |
michael@0 | 2278 | if (NS_SUCCEEDED(sourceAttributes->Item(i, getter_AddRefs(attr))) && attr) { |
michael@0 | 2279 | nsString sourceAttrName; |
michael@0 | 2280 | if (NS_SUCCEEDED(attr->GetName(sourceAttrName))) { |
michael@0 | 2281 | nsString sourceAttrValue; |
michael@0 | 2282 | /* Presence of an attribute in the named node map indicates that it was |
michael@0 | 2283 | * set on the element even if it has no value. |
michael@0 | 2284 | */ |
michael@0 | 2285 | if (NS_SUCCEEDED(attr->GetValue(sourceAttrValue))) { |
michael@0 | 2286 | if (destInBody) { |
michael@0 | 2287 | result = SetAttributeOrEquivalent(destElement, sourceAttrName, sourceAttrValue, false); |
michael@0 | 2288 | } else { |
michael@0 | 2289 | // the element is not inserted in the document yet, we don't want to put a |
michael@0 | 2290 | // transaction on the UndoStack |
michael@0 | 2291 | result = SetAttributeOrEquivalent(destElement, sourceAttrName, sourceAttrValue, true); |
michael@0 | 2292 | } |
michael@0 | 2293 | } else { |
michael@0 | 2294 | // Do we ever get here? |
michael@0 | 2295 | #if DEBUG_cmanske |
michael@0 | 2296 | printf("Attribute in sourceAttribute has empty value in nsEditor::CloneAttributes()\n"); |
michael@0 | 2297 | #endif |
michael@0 | 2298 | } |
michael@0 | 2299 | } |
michael@0 | 2300 | } |
michael@0 | 2301 | } |
michael@0 | 2302 | return result; |
michael@0 | 2303 | } |
michael@0 | 2304 | |
michael@0 | 2305 | |
michael@0 | 2306 | NS_IMETHODIMP nsEditor::ScrollSelectionIntoView(bool aScrollToAnchor) |
michael@0 | 2307 | { |
michael@0 | 2308 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 2309 | if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon) |
michael@0 | 2310 | { |
michael@0 | 2311 | int16_t region = nsISelectionController::SELECTION_FOCUS_REGION; |
michael@0 | 2312 | |
michael@0 | 2313 | if (aScrollToAnchor) |
michael@0 | 2314 | region = nsISelectionController::SELECTION_ANCHOR_REGION; |
michael@0 | 2315 | |
michael@0 | 2316 | selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, |
michael@0 | 2317 | region, nsISelectionController::SCROLL_OVERFLOW_HIDDEN); |
michael@0 | 2318 | } |
michael@0 | 2319 | |
michael@0 | 2320 | return NS_OK; |
michael@0 | 2321 | } |
michael@0 | 2322 | |
michael@0 | 2323 | NS_IMETHODIMP |
michael@0 | 2324 | nsEditor::InsertTextImpl(const nsAString& aStringToInsert, |
michael@0 | 2325 | nsCOMPtr<nsIDOMNode>* aInOutNode, |
michael@0 | 2326 | int32_t* aInOutOffset, |
michael@0 | 2327 | nsIDOMDocument* aDoc) |
michael@0 | 2328 | { |
michael@0 | 2329 | // NOTE: caller *must* have already used nsAutoTxnsConserveSelection |
michael@0 | 2330 | // stack-based class to turn off txn selection updating. Caller also turned |
michael@0 | 2331 | // on rules sniffing if desired. |
michael@0 | 2332 | |
michael@0 | 2333 | NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc, |
michael@0 | 2334 | NS_ERROR_NULL_POINTER); |
michael@0 | 2335 | if (!mComposition && aStringToInsert.IsEmpty()) { |
michael@0 | 2336 | return NS_OK; |
michael@0 | 2337 | } |
michael@0 | 2338 | |
michael@0 | 2339 | nsCOMPtr<nsINode> node = do_QueryInterface(*aInOutNode); |
michael@0 | 2340 | NS_ENSURE_STATE(node); |
michael@0 | 2341 | uint32_t offset = static_cast<uint32_t>(*aInOutOffset); |
michael@0 | 2342 | |
michael@0 | 2343 | if (!node->IsNodeOfType(nsINode::eTEXT) && IsPlaintextEditor()) { |
michael@0 | 2344 | nsCOMPtr<nsINode> root = GetRoot(); |
michael@0 | 2345 | // In some cases, node is the anonymous DIV, and offset is 0. To avoid |
michael@0 | 2346 | // injecting unneeded text nodes, we first look to see if we have one |
michael@0 | 2347 | // available. In that case, we'll just adjust node and offset accordingly. |
michael@0 | 2348 | if (node == root && offset == 0 && node->HasChildren() && |
michael@0 | 2349 | node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 2350 | node = node->GetFirstChild(); |
michael@0 | 2351 | } |
michael@0 | 2352 | // In some other cases, node is the anonymous DIV, and offset points to the |
michael@0 | 2353 | // terminating mozBR. In that case, we'll adjust aInOutNode and |
michael@0 | 2354 | // aInOutOffset to the preceding text node, if any. |
michael@0 | 2355 | if (node == root && offset > 0 && node->GetChildAt(offset - 1) && |
michael@0 | 2356 | node->GetChildAt(offset - 1)->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 2357 | node = node->GetChildAt(offset - 1); |
michael@0 | 2358 | offset = node->Length(); |
michael@0 | 2359 | } |
michael@0 | 2360 | // Sometimes, node is the mozBR element itself. In that case, we'll adjust |
michael@0 | 2361 | // the insertion point to the previous text node, if one exists, or to the |
michael@0 | 2362 | // parent anonymous DIV. |
michael@0 | 2363 | if (nsTextEditUtils::IsMozBR(node) && offset == 0) { |
michael@0 | 2364 | if (node->GetPreviousSibling() && |
michael@0 | 2365 | node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 2366 | node = node->GetPreviousSibling(); |
michael@0 | 2367 | offset = node->Length(); |
michael@0 | 2368 | } else if (node->GetParentNode() && node->GetParentNode() == root) { |
michael@0 | 2369 | node = node->GetParentNode(); |
michael@0 | 2370 | } |
michael@0 | 2371 | } |
michael@0 | 2372 | } |
michael@0 | 2373 | |
michael@0 | 2374 | nsresult res; |
michael@0 | 2375 | if (mComposition) { |
michael@0 | 2376 | if (!node->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 2377 | // create a text node |
michael@0 | 2378 | nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc); |
michael@0 | 2379 | NS_ENSURE_STATE(doc); |
michael@0 | 2380 | nsRefPtr<nsTextNode> newNode = doc->CreateTextNode(EmptyString()); |
michael@0 | 2381 | // then we insert it into the dom tree |
michael@0 | 2382 | res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset); |
michael@0 | 2383 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 2384 | node = newNode; |
michael@0 | 2385 | offset = 0; |
michael@0 | 2386 | } |
michael@0 | 2387 | nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(node); |
michael@0 | 2388 | NS_ENSURE_STATE(charDataNode); |
michael@0 | 2389 | res = InsertTextIntoTextNodeImpl(aStringToInsert, charDataNode, offset); |
michael@0 | 2390 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 2391 | offset += aStringToInsert.Length(); |
michael@0 | 2392 | } else { |
michael@0 | 2393 | if (node->IsNodeOfType(nsINode::eTEXT)) { |
michael@0 | 2394 | // we are inserting text into an existing text node. |
michael@0 | 2395 | nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(node); |
michael@0 | 2396 | NS_ENSURE_STATE(charDataNode); |
michael@0 | 2397 | res = InsertTextIntoTextNodeImpl(aStringToInsert, charDataNode, offset); |
michael@0 | 2398 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 2399 | offset += aStringToInsert.Length(); |
michael@0 | 2400 | } else { |
michael@0 | 2401 | // we are inserting text into a non-text node. first we have to create a |
michael@0 | 2402 | // textnode (this also populates it with the text) |
michael@0 | 2403 | nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc); |
michael@0 | 2404 | NS_ENSURE_STATE(doc); |
michael@0 | 2405 | nsRefPtr<nsTextNode> newNode = doc->CreateTextNode(aStringToInsert); |
michael@0 | 2406 | // then we insert it into the dom tree |
michael@0 | 2407 | res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset); |
michael@0 | 2408 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 2409 | node = newNode; |
michael@0 | 2410 | offset = aStringToInsert.Length(); |
michael@0 | 2411 | } |
michael@0 | 2412 | } |
michael@0 | 2413 | |
michael@0 | 2414 | *aInOutNode = node->AsDOMNode(); |
michael@0 | 2415 | *aInOutOffset = static_cast<int32_t>(offset); |
michael@0 | 2416 | return NS_OK; |
michael@0 | 2417 | } |
michael@0 | 2418 | |
michael@0 | 2419 | |
michael@0 | 2420 | nsresult nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert, |
michael@0 | 2421 | nsIDOMCharacterData *aTextNode, |
michael@0 | 2422 | int32_t aOffset, |
michael@0 | 2423 | bool aSuppressIME) |
michael@0 | 2424 | { |
michael@0 | 2425 | nsRefPtr<EditTxn> txn; |
michael@0 | 2426 | nsresult result = NS_OK; |
michael@0 | 2427 | bool isIMETransaction = false; |
michael@0 | 2428 | // aSuppressIME is used when editor must insert text, yet this text is not |
michael@0 | 2429 | // part of current ime operation. example: adjusting whitespace around an ime insertion. |
michael@0 | 2430 | if (mComposition && !aSuppressIME) { |
michael@0 | 2431 | if (!mIMETextNode) { |
michael@0 | 2432 | mIMETextNode = aTextNode; |
michael@0 | 2433 | mIMETextOffset = aOffset; |
michael@0 | 2434 | } |
michael@0 | 2435 | // Modify mPhonetic with raw text input clauses. |
michael@0 | 2436 | const TextRangeArray* ranges = mComposition->GetRanges(); |
michael@0 | 2437 | for (uint32_t i = 0; i < (ranges ? ranges->Length() : 0); ++i) { |
michael@0 | 2438 | const TextRange& textRange = ranges->ElementAt(i); |
michael@0 | 2439 | if (!textRange.Length() || |
michael@0 | 2440 | textRange.mRangeType != NS_TEXTRANGE_RAWINPUT) { |
michael@0 | 2441 | continue; |
michael@0 | 2442 | } |
michael@0 | 2443 | if (!mPhonetic) { |
michael@0 | 2444 | mPhonetic = new nsString(); |
michael@0 | 2445 | } |
michael@0 | 2446 | nsAutoString stringToInsert(aStringToInsert); |
michael@0 | 2447 | stringToInsert.Mid(*mPhonetic, |
michael@0 | 2448 | textRange.mStartOffset, textRange.Length()); |
michael@0 | 2449 | } |
michael@0 | 2450 | |
michael@0 | 2451 | nsRefPtr<IMETextTxn> imeTxn; |
michael@0 | 2452 | result = CreateTxnForIMEText(aStringToInsert, getter_AddRefs(imeTxn)); |
michael@0 | 2453 | txn = imeTxn; |
michael@0 | 2454 | isIMETransaction = true; |
michael@0 | 2455 | } |
michael@0 | 2456 | else |
michael@0 | 2457 | { |
michael@0 | 2458 | nsRefPtr<InsertTextTxn> insertTxn; |
michael@0 | 2459 | result = CreateTxnForInsertText(aStringToInsert, aTextNode, aOffset, |
michael@0 | 2460 | getter_AddRefs(insertTxn)); |
michael@0 | 2461 | txn = insertTxn; |
michael@0 | 2462 | } |
michael@0 | 2463 | NS_ENSURE_SUCCESS(result, result); |
michael@0 | 2464 | |
michael@0 | 2465 | // let listeners know what's up |
michael@0 | 2466 | int32_t i; |
michael@0 | 2467 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 2468 | mActionListeners[i]->WillInsertText(aTextNode, aOffset, aStringToInsert); |
michael@0 | 2469 | |
michael@0 | 2470 | // XXX we may not need these view batches anymore. This is handled at a higher level now I believe |
michael@0 | 2471 | BeginUpdateViewBatch(); |
michael@0 | 2472 | result = DoTransaction(txn); |
michael@0 | 2473 | EndUpdateViewBatch(); |
michael@0 | 2474 | |
michael@0 | 2475 | mRangeUpdater.SelAdjInsertText(aTextNode, aOffset, aStringToInsert); |
michael@0 | 2476 | |
michael@0 | 2477 | // let listeners know what happened |
michael@0 | 2478 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 2479 | mActionListeners[i]->DidInsertText(aTextNode, aOffset, aStringToInsert, result); |
michael@0 | 2480 | |
michael@0 | 2481 | // Added some cruft here for bug 43366. Layout was crashing because we left an |
michael@0 | 2482 | // empty text node lying around in the document. So I delete empty text nodes |
michael@0 | 2483 | // caused by IME. I have to mark the IME transaction as "fixed", which means |
michael@0 | 2484 | // that furure ime txns won't merge with it. This is because we don't want |
michael@0 | 2485 | // future ime txns trying to put their text into a node that is no longer in |
michael@0 | 2486 | // the document. This does not break undo/redo, because all these txns are |
michael@0 | 2487 | // wrapped in a parent PlaceHolder txn, and placeholder txns are already |
michael@0 | 2488 | // savvy to having multiple ime txns inside them. |
michael@0 | 2489 | |
michael@0 | 2490 | // delete empty ime text node if there is one |
michael@0 | 2491 | if (isIMETransaction && mIMETextNode) |
michael@0 | 2492 | { |
michael@0 | 2493 | uint32_t len; |
michael@0 | 2494 | mIMETextNode->GetLength(&len); |
michael@0 | 2495 | if (!len) |
michael@0 | 2496 | { |
michael@0 | 2497 | DeleteNode(mIMETextNode); |
michael@0 | 2498 | mIMETextNode = nullptr; |
michael@0 | 2499 | static_cast<IMETextTxn*>(txn.get())->MarkFixed(); // mark the ime txn "fixed" |
michael@0 | 2500 | } |
michael@0 | 2501 | } |
michael@0 | 2502 | |
michael@0 | 2503 | return result; |
michael@0 | 2504 | } |
michael@0 | 2505 | |
michael@0 | 2506 | |
michael@0 | 2507 | NS_IMETHODIMP nsEditor::SelectEntireDocument(nsISelection *aSelection) |
michael@0 | 2508 | { |
michael@0 | 2509 | if (!aSelection) { return NS_ERROR_NULL_POINTER; } |
michael@0 | 2510 | |
michael@0 | 2511 | nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot()); |
michael@0 | 2512 | if (!rootElement) { return NS_ERROR_NOT_INITIALIZED; } |
michael@0 | 2513 | |
michael@0 | 2514 | return aSelection->SelectAllChildren(rootElement); |
michael@0 | 2515 | } |
michael@0 | 2516 | |
michael@0 | 2517 | |
michael@0 | 2518 | nsINode* |
michael@0 | 2519 | nsEditor::GetFirstEditableNode(nsINode* aRoot) |
michael@0 | 2520 | { |
michael@0 | 2521 | MOZ_ASSERT(aRoot); |
michael@0 | 2522 | |
michael@0 | 2523 | nsIContent* node = GetLeftmostChild(aRoot); |
michael@0 | 2524 | if (node && !IsEditable(node)) { |
michael@0 | 2525 | node = GetNextNode(node, /* aEditableNode = */ true); |
michael@0 | 2526 | } |
michael@0 | 2527 | |
michael@0 | 2528 | return (node != aRoot) ? node : nullptr; |
michael@0 | 2529 | } |
michael@0 | 2530 | |
michael@0 | 2531 | |
michael@0 | 2532 | NS_IMETHODIMP |
michael@0 | 2533 | nsEditor::NotifyDocumentListeners(TDocumentListenerNotification aNotificationType) |
michael@0 | 2534 | { |
michael@0 | 2535 | int32_t numListeners = mDocStateListeners.Count(); |
michael@0 | 2536 | if (!numListeners) // maybe there just aren't any. |
michael@0 | 2537 | return NS_OK; |
michael@0 | 2538 | |
michael@0 | 2539 | nsCOMArray<nsIDocumentStateListener> listeners(mDocStateListeners); |
michael@0 | 2540 | nsresult rv = NS_OK; |
michael@0 | 2541 | int32_t i; |
michael@0 | 2542 | |
michael@0 | 2543 | switch (aNotificationType) |
michael@0 | 2544 | { |
michael@0 | 2545 | case eDocumentCreated: |
michael@0 | 2546 | for (i = 0; i < numListeners;i++) |
michael@0 | 2547 | { |
michael@0 | 2548 | rv = listeners[i]->NotifyDocumentCreated(); |
michael@0 | 2549 | if (NS_FAILED(rv)) |
michael@0 | 2550 | break; |
michael@0 | 2551 | } |
michael@0 | 2552 | break; |
michael@0 | 2553 | |
michael@0 | 2554 | case eDocumentToBeDestroyed: |
michael@0 | 2555 | for (i = 0; i < numListeners;i++) |
michael@0 | 2556 | { |
michael@0 | 2557 | rv = listeners[i]->NotifyDocumentWillBeDestroyed(); |
michael@0 | 2558 | if (NS_FAILED(rv)) |
michael@0 | 2559 | break; |
michael@0 | 2560 | } |
michael@0 | 2561 | break; |
michael@0 | 2562 | |
michael@0 | 2563 | case eDocumentStateChanged: |
michael@0 | 2564 | { |
michael@0 | 2565 | bool docIsDirty; |
michael@0 | 2566 | rv = GetDocumentModified(&docIsDirty); |
michael@0 | 2567 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2568 | |
michael@0 | 2569 | if (static_cast<int8_t>(docIsDirty) == mDocDirtyState) |
michael@0 | 2570 | return NS_OK; |
michael@0 | 2571 | |
michael@0 | 2572 | mDocDirtyState = docIsDirty; |
michael@0 | 2573 | |
michael@0 | 2574 | for (i = 0; i < numListeners;i++) |
michael@0 | 2575 | { |
michael@0 | 2576 | rv = listeners[i]->NotifyDocumentStateChanged(mDocDirtyState); |
michael@0 | 2577 | if (NS_FAILED(rv)) |
michael@0 | 2578 | break; |
michael@0 | 2579 | } |
michael@0 | 2580 | } |
michael@0 | 2581 | break; |
michael@0 | 2582 | |
michael@0 | 2583 | default: |
michael@0 | 2584 | NS_NOTREACHED("Unknown notification"); |
michael@0 | 2585 | } |
michael@0 | 2586 | |
michael@0 | 2587 | return rv; |
michael@0 | 2588 | } |
michael@0 | 2589 | |
michael@0 | 2590 | |
michael@0 | 2591 | NS_IMETHODIMP nsEditor::CreateTxnForInsertText(const nsAString & aStringToInsert, |
michael@0 | 2592 | nsIDOMCharacterData *aTextNode, |
michael@0 | 2593 | int32_t aOffset, |
michael@0 | 2594 | InsertTextTxn ** aTxn) |
michael@0 | 2595 | { |
michael@0 | 2596 | NS_ENSURE_TRUE(aTextNode && aTxn, NS_ERROR_NULL_POINTER); |
michael@0 | 2597 | nsresult rv; |
michael@0 | 2598 | |
michael@0 | 2599 | nsRefPtr<InsertTextTxn> txn = new InsertTextTxn(); |
michael@0 | 2600 | rv = txn->Init(aTextNode, aOffset, aStringToInsert, this); |
michael@0 | 2601 | if (NS_SUCCEEDED(rv)) |
michael@0 | 2602 | { |
michael@0 | 2603 | txn.forget(aTxn); |
michael@0 | 2604 | } |
michael@0 | 2605 | |
michael@0 | 2606 | return rv; |
michael@0 | 2607 | } |
michael@0 | 2608 | |
michael@0 | 2609 | |
michael@0 | 2610 | NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement, |
michael@0 | 2611 | uint32_t aOffset, |
michael@0 | 2612 | uint32_t aLength) |
michael@0 | 2613 | { |
michael@0 | 2614 | nsRefPtr<DeleteTextTxn> txn; |
michael@0 | 2615 | nsresult result = CreateTxnForDeleteText(aElement, aOffset, aLength, |
michael@0 | 2616 | getter_AddRefs(txn)); |
michael@0 | 2617 | nsAutoRules beginRulesSniffing(this, EditAction::deleteText, nsIEditor::ePrevious); |
michael@0 | 2618 | if (NS_SUCCEEDED(result)) |
michael@0 | 2619 | { |
michael@0 | 2620 | // let listeners know what's up |
michael@0 | 2621 | int32_t i; |
michael@0 | 2622 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 2623 | mActionListeners[i]->WillDeleteText(aElement, aOffset, aLength); |
michael@0 | 2624 | |
michael@0 | 2625 | result = DoTransaction(txn); |
michael@0 | 2626 | |
michael@0 | 2627 | // let listeners know what happened |
michael@0 | 2628 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 2629 | mActionListeners[i]->DidDeleteText(aElement, aOffset, aLength, result); |
michael@0 | 2630 | } |
michael@0 | 2631 | return result; |
michael@0 | 2632 | } |
michael@0 | 2633 | |
michael@0 | 2634 | |
michael@0 | 2635 | nsresult |
michael@0 | 2636 | nsEditor::CreateTxnForDeleteText(nsIDOMCharacterData* aElement, |
michael@0 | 2637 | uint32_t aOffset, |
michael@0 | 2638 | uint32_t aLength, |
michael@0 | 2639 | DeleteTextTxn** aTxn) |
michael@0 | 2640 | { |
michael@0 | 2641 | NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
michael@0 | 2642 | |
michael@0 | 2643 | nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn(); |
michael@0 | 2644 | |
michael@0 | 2645 | nsresult res = txn->Init(this, aElement, aOffset, aLength, &mRangeUpdater); |
michael@0 | 2646 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 2647 | |
michael@0 | 2648 | txn.forget(aTxn); |
michael@0 | 2649 | return NS_OK; |
michael@0 | 2650 | } |
michael@0 | 2651 | |
michael@0 | 2652 | |
michael@0 | 2653 | |
michael@0 | 2654 | |
michael@0 | 2655 | NS_IMETHODIMP nsEditor::CreateTxnForSplitNode(nsIDOMNode *aNode, |
michael@0 | 2656 | uint32_t aOffset, |
michael@0 | 2657 | SplitElementTxn **aTxn) |
michael@0 | 2658 | { |
michael@0 | 2659 | NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
michael@0 | 2660 | |
michael@0 | 2661 | nsRefPtr<SplitElementTxn> txn = new SplitElementTxn(); |
michael@0 | 2662 | |
michael@0 | 2663 | nsresult rv = txn->Init(this, aNode, aOffset); |
michael@0 | 2664 | if (NS_SUCCEEDED(rv)) |
michael@0 | 2665 | { |
michael@0 | 2666 | txn.forget(aTxn); |
michael@0 | 2667 | } |
michael@0 | 2668 | |
michael@0 | 2669 | return rv; |
michael@0 | 2670 | } |
michael@0 | 2671 | |
michael@0 | 2672 | NS_IMETHODIMP nsEditor::CreateTxnForJoinNode(nsIDOMNode *aLeftNode, |
michael@0 | 2673 | nsIDOMNode *aRightNode, |
michael@0 | 2674 | JoinElementTxn **aTxn) |
michael@0 | 2675 | { |
michael@0 | 2676 | NS_ENSURE_TRUE(aLeftNode && aRightNode, NS_ERROR_NULL_POINTER); |
michael@0 | 2677 | |
michael@0 | 2678 | nsRefPtr<JoinElementTxn> txn = new JoinElementTxn(); |
michael@0 | 2679 | |
michael@0 | 2680 | nsresult rv = txn->Init(this, aLeftNode, aRightNode); |
michael@0 | 2681 | if (NS_SUCCEEDED(rv)) |
michael@0 | 2682 | { |
michael@0 | 2683 | txn.forget(aTxn); |
michael@0 | 2684 | } |
michael@0 | 2685 | |
michael@0 | 2686 | return rv; |
michael@0 | 2687 | } |
michael@0 | 2688 | |
michael@0 | 2689 | |
michael@0 | 2690 | // END nsEditor core implementation |
michael@0 | 2691 | |
michael@0 | 2692 | |
michael@0 | 2693 | // BEGIN nsEditor public helper methods |
michael@0 | 2694 | |
michael@0 | 2695 | nsresult |
michael@0 | 2696 | nsEditor::SplitNodeImpl(nsIDOMNode * aExistingRightNode, |
michael@0 | 2697 | int32_t aOffset, |
michael@0 | 2698 | nsIDOMNode* aNewLeftNode, |
michael@0 | 2699 | nsIDOMNode* aParent) |
michael@0 | 2700 | { |
michael@0 | 2701 | NS_ASSERTION(((nullptr!=aExistingRightNode) && |
michael@0 | 2702 | (nullptr!=aNewLeftNode) && |
michael@0 | 2703 | (nullptr!=aParent)), |
michael@0 | 2704 | "null arg"); |
michael@0 | 2705 | nsresult result; |
michael@0 | 2706 | if ((nullptr!=aExistingRightNode) && |
michael@0 | 2707 | (nullptr!=aNewLeftNode) && |
michael@0 | 2708 | (nullptr!=aParent)) |
michael@0 | 2709 | { |
michael@0 | 2710 | // get selection |
michael@0 | 2711 | nsCOMPtr<nsISelection> selection; |
michael@0 | 2712 | result = GetSelection(getter_AddRefs(selection)); |
michael@0 | 2713 | NS_ENSURE_SUCCESS(result, result); |
michael@0 | 2714 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 2715 | |
michael@0 | 2716 | // remember some selection points |
michael@0 | 2717 | nsCOMPtr<nsIDOMNode> selStartNode, selEndNode; |
michael@0 | 2718 | int32_t selStartOffset, selEndOffset; |
michael@0 | 2719 | result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset); |
michael@0 | 2720 | if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that |
michael@0 | 2721 | result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset); |
michael@0 | 2722 | if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that |
michael@0 | 2723 | |
michael@0 | 2724 | nsCOMPtr<nsIDOMNode> resultNode; |
michael@0 | 2725 | result = aParent->InsertBefore(aNewLeftNode, aExistingRightNode, getter_AddRefs(resultNode)); |
michael@0 | 2726 | //printf(" after insert\n"); content->List(); // DEBUG |
michael@0 | 2727 | if (NS_SUCCEEDED(result)) |
michael@0 | 2728 | { |
michael@0 | 2729 | // split the children between the 2 nodes |
michael@0 | 2730 | // at this point, aExistingRightNode has all the children |
michael@0 | 2731 | // move all the children whose index is < aOffset to aNewLeftNode |
michael@0 | 2732 | if (0<=aOffset) // don't bother unless we're going to move at least one child |
michael@0 | 2733 | { |
michael@0 | 2734 | // if it's a text node, just shuffle around some text |
michael@0 | 2735 | nsCOMPtr<nsIDOMCharacterData> rightNodeAsText( do_QueryInterface(aExistingRightNode) ); |
michael@0 | 2736 | nsCOMPtr<nsIDOMCharacterData> leftNodeAsText( do_QueryInterface(aNewLeftNode) ); |
michael@0 | 2737 | if (leftNodeAsText && rightNodeAsText) |
michael@0 | 2738 | { |
michael@0 | 2739 | // fix right node |
michael@0 | 2740 | nsAutoString leftText; |
michael@0 | 2741 | rightNodeAsText->SubstringData(0, aOffset, leftText); |
michael@0 | 2742 | rightNodeAsText->DeleteData(0, aOffset); |
michael@0 | 2743 | // fix left node |
michael@0 | 2744 | leftNodeAsText->SetData(leftText); |
michael@0 | 2745 | // moose |
michael@0 | 2746 | } |
michael@0 | 2747 | else |
michael@0 | 2748 | { // otherwise it's an interior node, so shuffle around the children |
michael@0 | 2749 | // go through list backwards so deletes don't interfere with the iteration |
michael@0 | 2750 | nsCOMPtr<nsIDOMNodeList> childNodes; |
michael@0 | 2751 | result = aExistingRightNode->GetChildNodes(getter_AddRefs(childNodes)); |
michael@0 | 2752 | if ((NS_SUCCEEDED(result)) && (childNodes)) |
michael@0 | 2753 | { |
michael@0 | 2754 | int32_t i=aOffset-1; |
michael@0 | 2755 | for ( ; ((NS_SUCCEEDED(result)) && (0<=i)); i--) |
michael@0 | 2756 | { |
michael@0 | 2757 | nsCOMPtr<nsIDOMNode> childNode; |
michael@0 | 2758 | result = childNodes->Item(i, getter_AddRefs(childNode)); |
michael@0 | 2759 | if ((NS_SUCCEEDED(result)) && (childNode)) |
michael@0 | 2760 | { |
michael@0 | 2761 | result = aExistingRightNode->RemoveChild(childNode, getter_AddRefs(resultNode)); |
michael@0 | 2762 | //printf(" after remove\n"); content->List(); // DEBUG |
michael@0 | 2763 | if (NS_SUCCEEDED(result)) |
michael@0 | 2764 | { |
michael@0 | 2765 | nsCOMPtr<nsIDOMNode> firstChild; |
michael@0 | 2766 | aNewLeftNode->GetFirstChild(getter_AddRefs(firstChild)); |
michael@0 | 2767 | result = aNewLeftNode->InsertBefore(childNode, firstChild, getter_AddRefs(resultNode)); |
michael@0 | 2768 | //printf(" after append\n"); content->List(); // DEBUG |
michael@0 | 2769 | } |
michael@0 | 2770 | } |
michael@0 | 2771 | } |
michael@0 | 2772 | } |
michael@0 | 2773 | } |
michael@0 | 2774 | // handle selection |
michael@0 | 2775 | nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
michael@0 | 2776 | if (ps) |
michael@0 | 2777 | ps->FlushPendingNotifications(Flush_Frames); |
michael@0 | 2778 | |
michael@0 | 2779 | if (GetShouldTxnSetSelection()) |
michael@0 | 2780 | { |
michael@0 | 2781 | // editor wants us to set selection at split point |
michael@0 | 2782 | selection->Collapse(aNewLeftNode, aOffset); |
michael@0 | 2783 | } |
michael@0 | 2784 | else if (selStartNode) |
michael@0 | 2785 | { |
michael@0 | 2786 | // else adjust the selection if needed. if selStartNode is null, then there was no selection. |
michael@0 | 2787 | // HACK: this is overly simplified - multi-range selections need more work than this |
michael@0 | 2788 | if (selStartNode.get() == aExistingRightNode) |
michael@0 | 2789 | { |
michael@0 | 2790 | if (selStartOffset < aOffset) |
michael@0 | 2791 | { |
michael@0 | 2792 | selStartNode = aNewLeftNode; |
michael@0 | 2793 | } |
michael@0 | 2794 | else |
michael@0 | 2795 | { |
michael@0 | 2796 | selStartOffset -= aOffset; |
michael@0 | 2797 | } |
michael@0 | 2798 | } |
michael@0 | 2799 | if (selEndNode.get() == aExistingRightNode) |
michael@0 | 2800 | { |
michael@0 | 2801 | if (selEndOffset < aOffset) |
michael@0 | 2802 | { |
michael@0 | 2803 | selEndNode = aNewLeftNode; |
michael@0 | 2804 | } |
michael@0 | 2805 | else |
michael@0 | 2806 | { |
michael@0 | 2807 | selEndOffset -= aOffset; |
michael@0 | 2808 | } |
michael@0 | 2809 | } |
michael@0 | 2810 | selection->Collapse(selStartNode,selStartOffset); |
michael@0 | 2811 | selection->Extend(selEndNode,selEndOffset); |
michael@0 | 2812 | } |
michael@0 | 2813 | } |
michael@0 | 2814 | } |
michael@0 | 2815 | } |
michael@0 | 2816 | else |
michael@0 | 2817 | result = NS_ERROR_INVALID_ARG; |
michael@0 | 2818 | |
michael@0 | 2819 | return result; |
michael@0 | 2820 | } |
michael@0 | 2821 | |
michael@0 | 2822 | nsresult |
michael@0 | 2823 | nsEditor::JoinNodesImpl(nsINode* aNodeToKeep, |
michael@0 | 2824 | nsINode* aNodeToJoin, |
michael@0 | 2825 | nsINode* aParent) |
michael@0 | 2826 | { |
michael@0 | 2827 | MOZ_ASSERT(aNodeToKeep); |
michael@0 | 2828 | MOZ_ASSERT(aNodeToJoin); |
michael@0 | 2829 | MOZ_ASSERT(aParent); |
michael@0 | 2830 | |
michael@0 | 2831 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 2832 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 2833 | |
michael@0 | 2834 | // remember some selection points |
michael@0 | 2835 | nsCOMPtr<nsINode> selStartNode; |
michael@0 | 2836 | int32_t selStartOffset; |
michael@0 | 2837 | nsresult result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset); |
michael@0 | 2838 | if (NS_FAILED(result)) { |
michael@0 | 2839 | selStartNode = nullptr; |
michael@0 | 2840 | } |
michael@0 | 2841 | |
michael@0 | 2842 | nsCOMPtr<nsINode> selEndNode; |
michael@0 | 2843 | int32_t selEndOffset; |
michael@0 | 2844 | result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset); |
michael@0 | 2845 | // Joe or Kin should comment here on why the following line is not a copy/paste error |
michael@0 | 2846 | if (NS_FAILED(result)) { |
michael@0 | 2847 | selStartNode = nullptr; |
michael@0 | 2848 | } |
michael@0 | 2849 | |
michael@0 | 2850 | uint32_t firstNodeLength = aNodeToJoin->Length(); |
michael@0 | 2851 | |
michael@0 | 2852 | int32_t joinOffset; |
michael@0 | 2853 | GetNodeLocation(aNodeToJoin, &joinOffset); |
michael@0 | 2854 | int32_t keepOffset; |
michael@0 | 2855 | nsINode* parent = GetNodeLocation(aNodeToKeep, &keepOffset); |
michael@0 | 2856 | |
michael@0 | 2857 | // if selection endpoint is between the nodes, remember it as being |
michael@0 | 2858 | // in the one that is going away instead. This simplifies later selection |
michael@0 | 2859 | // adjustment logic at end of this method. |
michael@0 | 2860 | if (selStartNode) { |
michael@0 | 2861 | if (selStartNode == parent && |
michael@0 | 2862 | joinOffset < selStartOffset && selStartOffset <= keepOffset) { |
michael@0 | 2863 | selStartNode = aNodeToJoin; |
michael@0 | 2864 | selStartOffset = firstNodeLength; |
michael@0 | 2865 | } |
michael@0 | 2866 | if (selEndNode == parent && |
michael@0 | 2867 | joinOffset < selEndOffset && selEndOffset <= keepOffset) { |
michael@0 | 2868 | selEndNode = aNodeToJoin; |
michael@0 | 2869 | selEndOffset = firstNodeLength; |
michael@0 | 2870 | } |
michael@0 | 2871 | } |
michael@0 | 2872 | |
michael@0 | 2873 | // ok, ready to do join now. |
michael@0 | 2874 | // if it's a text node, just shuffle around some text |
michael@0 | 2875 | nsCOMPtr<nsIDOMCharacterData> keepNodeAsText( do_QueryInterface(aNodeToKeep) ); |
michael@0 | 2876 | nsCOMPtr<nsIDOMCharacterData> joinNodeAsText( do_QueryInterface(aNodeToJoin) ); |
michael@0 | 2877 | if (keepNodeAsText && joinNodeAsText) { |
michael@0 | 2878 | nsAutoString rightText; |
michael@0 | 2879 | nsAutoString leftText; |
michael@0 | 2880 | keepNodeAsText->GetData(rightText); |
michael@0 | 2881 | joinNodeAsText->GetData(leftText); |
michael@0 | 2882 | leftText += rightText; |
michael@0 | 2883 | keepNodeAsText->SetData(leftText); |
michael@0 | 2884 | } else { |
michael@0 | 2885 | // otherwise it's an interior node, so shuffle around the children |
michael@0 | 2886 | nsCOMPtr<nsINodeList> childNodes = aNodeToJoin->ChildNodes(); |
michael@0 | 2887 | MOZ_ASSERT(childNodes); |
michael@0 | 2888 | |
michael@0 | 2889 | // remember the first child in aNodeToKeep, we'll insert all the children of aNodeToJoin in front of it |
michael@0 | 2890 | // GetFirstChild returns nullptr firstNode if aNodeToKeep has no children, that's ok. |
michael@0 | 2891 | nsCOMPtr<nsIContent> firstNode = aNodeToKeep->GetFirstChild(); |
michael@0 | 2892 | |
michael@0 | 2893 | // have to go through the list backwards to keep deletes from interfering with iteration |
michael@0 | 2894 | for (uint32_t i = childNodes->Length(); i > 0; --i) { |
michael@0 | 2895 | nsCOMPtr<nsIContent> childNode = childNodes->Item(i - 1); |
michael@0 | 2896 | if (childNode) { |
michael@0 | 2897 | // prepend children of aNodeToJoin |
michael@0 | 2898 | ErrorResult err; |
michael@0 | 2899 | aNodeToKeep->InsertBefore(*childNode, firstNode, err); |
michael@0 | 2900 | NS_ENSURE_SUCCESS(err.ErrorCode(), err.ErrorCode()); |
michael@0 | 2901 | firstNode = childNode.forget(); |
michael@0 | 2902 | } |
michael@0 | 2903 | } |
michael@0 | 2904 | } |
michael@0 | 2905 | |
michael@0 | 2906 | // delete the extra node |
michael@0 | 2907 | ErrorResult err; |
michael@0 | 2908 | aParent->RemoveChild(*aNodeToJoin, err); |
michael@0 | 2909 | |
michael@0 | 2910 | if (GetShouldTxnSetSelection()) { |
michael@0 | 2911 | // editor wants us to set selection at join point |
michael@0 | 2912 | selection->Collapse(aNodeToKeep, SafeCast<int32_t>(firstNodeLength)); |
michael@0 | 2913 | } else if (selStartNode) { |
michael@0 | 2914 | // and adjust the selection if needed |
michael@0 | 2915 | // HACK: this is overly simplified - multi-range selections need more work than this |
michael@0 | 2916 | bool bNeedToAdjust = false; |
michael@0 | 2917 | |
michael@0 | 2918 | // check to see if we joined nodes where selection starts |
michael@0 | 2919 | if (selStartNode == aNodeToJoin) { |
michael@0 | 2920 | bNeedToAdjust = true; |
michael@0 | 2921 | selStartNode = aNodeToKeep; |
michael@0 | 2922 | } else if (selStartNode == aNodeToKeep) { |
michael@0 | 2923 | bNeedToAdjust = true; |
michael@0 | 2924 | selStartOffset += firstNodeLength; |
michael@0 | 2925 | } |
michael@0 | 2926 | |
michael@0 | 2927 | // check to see if we joined nodes where selection ends |
michael@0 | 2928 | if (selEndNode == aNodeToJoin) { |
michael@0 | 2929 | bNeedToAdjust = true; |
michael@0 | 2930 | selEndNode = aNodeToKeep; |
michael@0 | 2931 | } else if (selEndNode == aNodeToKeep) { |
michael@0 | 2932 | bNeedToAdjust = true; |
michael@0 | 2933 | selEndOffset += firstNodeLength; |
michael@0 | 2934 | } |
michael@0 | 2935 | |
michael@0 | 2936 | // adjust selection if needed |
michael@0 | 2937 | if (bNeedToAdjust) { |
michael@0 | 2938 | selection->Collapse(selStartNode, selStartOffset); |
michael@0 | 2939 | selection->Extend(selEndNode, selEndOffset); |
michael@0 | 2940 | } |
michael@0 | 2941 | } |
michael@0 | 2942 | |
michael@0 | 2943 | return err.ErrorCode(); |
michael@0 | 2944 | } |
michael@0 | 2945 | |
michael@0 | 2946 | |
michael@0 | 2947 | int32_t |
michael@0 | 2948 | nsEditor::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent) |
michael@0 | 2949 | { |
michael@0 | 2950 | MOZ_ASSERT(aChild && aParent); |
michael@0 | 2951 | |
michael@0 | 2952 | nsCOMPtr<nsINode> parent = do_QueryInterface(aParent); |
michael@0 | 2953 | nsCOMPtr<nsINode> child = do_QueryInterface(aChild); |
michael@0 | 2954 | MOZ_ASSERT(parent && child); |
michael@0 | 2955 | |
michael@0 | 2956 | int32_t idx = parent->IndexOf(child); |
michael@0 | 2957 | MOZ_ASSERT(idx != -1); |
michael@0 | 2958 | return idx; |
michael@0 | 2959 | } |
michael@0 | 2960 | |
michael@0 | 2961 | // static |
michael@0 | 2962 | already_AddRefed<nsIDOMNode> |
michael@0 | 2963 | nsEditor::GetNodeLocation(nsIDOMNode* aChild, int32_t* outOffset) |
michael@0 | 2964 | { |
michael@0 | 2965 | MOZ_ASSERT(aChild && outOffset); |
michael@0 | 2966 | NS_ENSURE_TRUE(aChild && outOffset, nullptr); |
michael@0 | 2967 | *outOffset = -1; |
michael@0 | 2968 | |
michael@0 | 2969 | nsCOMPtr<nsIDOMNode> parent; |
michael@0 | 2970 | |
michael@0 | 2971 | MOZ_ALWAYS_TRUE(NS_SUCCEEDED( |
michael@0 | 2972 | aChild->GetParentNode(getter_AddRefs(parent)))); |
michael@0 | 2973 | if (parent) { |
michael@0 | 2974 | *outOffset = GetChildOffset(aChild, parent); |
michael@0 | 2975 | } |
michael@0 | 2976 | |
michael@0 | 2977 | return parent.forget(); |
michael@0 | 2978 | } |
michael@0 | 2979 | |
michael@0 | 2980 | nsINode* |
michael@0 | 2981 | nsEditor::GetNodeLocation(nsINode* aChild, int32_t* aOffset) |
michael@0 | 2982 | { |
michael@0 | 2983 | MOZ_ASSERT(aChild); |
michael@0 | 2984 | MOZ_ASSERT(aOffset); |
michael@0 | 2985 | |
michael@0 | 2986 | nsINode* parent = aChild->GetParentNode(); |
michael@0 | 2987 | if (parent) { |
michael@0 | 2988 | *aOffset = parent->IndexOf(aChild); |
michael@0 | 2989 | MOZ_ASSERT(*aOffset != -1); |
michael@0 | 2990 | } else { |
michael@0 | 2991 | *aOffset = -1; |
michael@0 | 2992 | } |
michael@0 | 2993 | return parent; |
michael@0 | 2994 | } |
michael@0 | 2995 | |
michael@0 | 2996 | // returns the number of things inside aNode. |
michael@0 | 2997 | // If aNode is text, returns number of characters. If not, returns number of children nodes. |
michael@0 | 2998 | nsresult |
michael@0 | 2999 | nsEditor::GetLengthOfDOMNode(nsIDOMNode *aNode, uint32_t &aCount) |
michael@0 | 3000 | { |
michael@0 | 3001 | aCount = 0; |
michael@0 | 3002 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 3003 | NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
michael@0 | 3004 | aCount = node->Length(); |
michael@0 | 3005 | return NS_OK; |
michael@0 | 3006 | } |
michael@0 | 3007 | |
michael@0 | 3008 | |
michael@0 | 3009 | nsresult |
michael@0 | 3010 | nsEditor::GetPriorNode(nsIDOMNode *aParentNode, |
michael@0 | 3011 | int32_t aOffset, |
michael@0 | 3012 | bool aEditableNode, |
michael@0 | 3013 | nsCOMPtr<nsIDOMNode> *aResultNode, |
michael@0 | 3014 | bool bNoBlockCrossing) |
michael@0 | 3015 | { |
michael@0 | 3016 | NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3017 | *aResultNode = nullptr; |
michael@0 | 3018 | |
michael@0 | 3019 | nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode); |
michael@0 | 3020 | NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3021 | |
michael@0 | 3022 | *aResultNode = do_QueryInterface(GetPriorNode(parentNode, aOffset, |
michael@0 | 3023 | aEditableNode, |
michael@0 | 3024 | bNoBlockCrossing)); |
michael@0 | 3025 | return NS_OK; |
michael@0 | 3026 | } |
michael@0 | 3027 | |
michael@0 | 3028 | nsIContent* |
michael@0 | 3029 | nsEditor::GetPriorNode(nsINode* aParentNode, |
michael@0 | 3030 | int32_t aOffset, |
michael@0 | 3031 | bool aEditableNode, |
michael@0 | 3032 | bool aNoBlockCrossing) |
michael@0 | 3033 | { |
michael@0 | 3034 | MOZ_ASSERT(aParentNode); |
michael@0 | 3035 | |
michael@0 | 3036 | // If we are at the beginning of the node, or it is a text node, then just |
michael@0 | 3037 | // look before it. |
michael@0 | 3038 | if (!aOffset || aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) { |
michael@0 | 3039 | if (aNoBlockCrossing && IsBlockNode(aParentNode)) { |
michael@0 | 3040 | // If we aren't allowed to cross blocks, don't look before this block. |
michael@0 | 3041 | return nullptr; |
michael@0 | 3042 | } |
michael@0 | 3043 | return GetPriorNode(aParentNode, aEditableNode, aNoBlockCrossing); |
michael@0 | 3044 | } |
michael@0 | 3045 | |
michael@0 | 3046 | // else look before the child at 'aOffset' |
michael@0 | 3047 | if (nsIContent* child = aParentNode->GetChildAt(aOffset)) { |
michael@0 | 3048 | return GetPriorNode(child, aEditableNode, aNoBlockCrossing); |
michael@0 | 3049 | } |
michael@0 | 3050 | |
michael@0 | 3051 | // unless there isn't one, in which case we are at the end of the node |
michael@0 | 3052 | // and want the deep-right child. |
michael@0 | 3053 | nsIContent* resultNode = GetRightmostChild(aParentNode, aNoBlockCrossing); |
michael@0 | 3054 | if (!resultNode || !aEditableNode || IsEditable(resultNode)) { |
michael@0 | 3055 | return resultNode; |
michael@0 | 3056 | } |
michael@0 | 3057 | |
michael@0 | 3058 | // restart the search from the non-editable node we just found |
michael@0 | 3059 | return GetPriorNode(resultNode, aEditableNode, aNoBlockCrossing); |
michael@0 | 3060 | } |
michael@0 | 3061 | |
michael@0 | 3062 | |
michael@0 | 3063 | nsresult |
michael@0 | 3064 | nsEditor::GetNextNode(nsIDOMNode *aParentNode, |
michael@0 | 3065 | int32_t aOffset, |
michael@0 | 3066 | bool aEditableNode, |
michael@0 | 3067 | nsCOMPtr<nsIDOMNode> *aResultNode, |
michael@0 | 3068 | bool bNoBlockCrossing) |
michael@0 | 3069 | { |
michael@0 | 3070 | NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3071 | *aResultNode = nullptr; |
michael@0 | 3072 | |
michael@0 | 3073 | nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode); |
michael@0 | 3074 | NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3075 | |
michael@0 | 3076 | *aResultNode = do_QueryInterface(GetNextNode(parentNode, aOffset, |
michael@0 | 3077 | aEditableNode, |
michael@0 | 3078 | bNoBlockCrossing)); |
michael@0 | 3079 | return NS_OK; |
michael@0 | 3080 | } |
michael@0 | 3081 | |
michael@0 | 3082 | nsIContent* |
michael@0 | 3083 | nsEditor::GetNextNode(nsINode* aParentNode, |
michael@0 | 3084 | int32_t aOffset, |
michael@0 | 3085 | bool aEditableNode, |
michael@0 | 3086 | bool aNoBlockCrossing) |
michael@0 | 3087 | { |
michael@0 | 3088 | MOZ_ASSERT(aParentNode); |
michael@0 | 3089 | |
michael@0 | 3090 | // if aParentNode is a text node, use its location instead |
michael@0 | 3091 | if (aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) { |
michael@0 | 3092 | nsINode* parent = aParentNode->GetParentNode(); |
michael@0 | 3093 | NS_ENSURE_TRUE(parent, nullptr); |
michael@0 | 3094 | aOffset = parent->IndexOf(aParentNode) + 1; // _after_ the text node |
michael@0 | 3095 | aParentNode = parent; |
michael@0 | 3096 | } |
michael@0 | 3097 | |
michael@0 | 3098 | // look at the child at 'aOffset' |
michael@0 | 3099 | nsIContent* child = aParentNode->GetChildAt(aOffset); |
michael@0 | 3100 | if (child) { |
michael@0 | 3101 | if (aNoBlockCrossing && IsBlockNode(child)) { |
michael@0 | 3102 | return child; |
michael@0 | 3103 | } |
michael@0 | 3104 | |
michael@0 | 3105 | nsIContent* resultNode = GetLeftmostChild(child, aNoBlockCrossing); |
michael@0 | 3106 | if (!resultNode) { |
michael@0 | 3107 | return child; |
michael@0 | 3108 | } |
michael@0 | 3109 | |
michael@0 | 3110 | if (!IsDescendantOfEditorRoot(resultNode)) { |
michael@0 | 3111 | return nullptr; |
michael@0 | 3112 | } |
michael@0 | 3113 | |
michael@0 | 3114 | if (!aEditableNode || IsEditable(resultNode)) { |
michael@0 | 3115 | return resultNode; |
michael@0 | 3116 | } |
michael@0 | 3117 | |
michael@0 | 3118 | // restart the search from the non-editable node we just found |
michael@0 | 3119 | return GetNextNode(resultNode, aEditableNode, aNoBlockCrossing); |
michael@0 | 3120 | } |
michael@0 | 3121 | |
michael@0 | 3122 | // unless there isn't one, in which case we are at the end of the node |
michael@0 | 3123 | // and want the next one. |
michael@0 | 3124 | if (aNoBlockCrossing && IsBlockNode(aParentNode)) { |
michael@0 | 3125 | // don't cross out of parent block |
michael@0 | 3126 | return nullptr; |
michael@0 | 3127 | } |
michael@0 | 3128 | |
michael@0 | 3129 | return GetNextNode(aParentNode, aEditableNode, aNoBlockCrossing); |
michael@0 | 3130 | } |
michael@0 | 3131 | |
michael@0 | 3132 | |
michael@0 | 3133 | nsresult |
michael@0 | 3134 | nsEditor::GetPriorNode(nsIDOMNode *aCurrentNode, |
michael@0 | 3135 | bool aEditableNode, |
michael@0 | 3136 | nsCOMPtr<nsIDOMNode> *aResultNode, |
michael@0 | 3137 | bool bNoBlockCrossing) |
michael@0 | 3138 | { |
michael@0 | 3139 | NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3140 | |
michael@0 | 3141 | nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode); |
michael@0 | 3142 | NS_ENSURE_TRUE(currentNode, NS_ERROR_NULL_POINTER); |
michael@0 | 3143 | |
michael@0 | 3144 | *aResultNode = do_QueryInterface(GetPriorNode(currentNode, aEditableNode, |
michael@0 | 3145 | bNoBlockCrossing)); |
michael@0 | 3146 | return NS_OK; |
michael@0 | 3147 | } |
michael@0 | 3148 | |
michael@0 | 3149 | nsIContent* |
michael@0 | 3150 | nsEditor::GetPriorNode(nsINode* aCurrentNode, bool aEditableNode, |
michael@0 | 3151 | bool aNoBlockCrossing /* = false */) |
michael@0 | 3152 | { |
michael@0 | 3153 | MOZ_ASSERT(aCurrentNode); |
michael@0 | 3154 | |
michael@0 | 3155 | if (!IsDescendantOfEditorRoot(aCurrentNode)) { |
michael@0 | 3156 | return nullptr; |
michael@0 | 3157 | } |
michael@0 | 3158 | |
michael@0 | 3159 | return FindNode(aCurrentNode, false, aEditableNode, aNoBlockCrossing); |
michael@0 | 3160 | } |
michael@0 | 3161 | |
michael@0 | 3162 | nsIContent* |
michael@0 | 3163 | nsEditor::FindNextLeafNode(nsINode *aCurrentNode, |
michael@0 | 3164 | bool aGoForward, |
michael@0 | 3165 | bool bNoBlockCrossing) |
michael@0 | 3166 | { |
michael@0 | 3167 | // called only by GetPriorNode so we don't need to check params. |
michael@0 | 3168 | NS_PRECONDITION(IsDescendantOfEditorRoot(aCurrentNode) && |
michael@0 | 3169 | !IsEditorRoot(aCurrentNode), |
michael@0 | 3170 | "Bogus arguments"); |
michael@0 | 3171 | |
michael@0 | 3172 | nsINode* cur = aCurrentNode; |
michael@0 | 3173 | for (;;) { |
michael@0 | 3174 | // if aCurrentNode has a sibling in the right direction, return |
michael@0 | 3175 | // that sibling's closest child (or itself if it has no children) |
michael@0 | 3176 | nsIContent* sibling = |
michael@0 | 3177 | aGoForward ? cur->GetNextSibling() : cur->GetPreviousSibling(); |
michael@0 | 3178 | if (sibling) { |
michael@0 | 3179 | if (bNoBlockCrossing && IsBlockNode(sibling)) { |
michael@0 | 3180 | // don't look inside prevsib, since it is a block |
michael@0 | 3181 | return sibling; |
michael@0 | 3182 | } |
michael@0 | 3183 | nsIContent *leaf = |
michael@0 | 3184 | aGoForward ? GetLeftmostChild(sibling, bNoBlockCrossing) : |
michael@0 | 3185 | GetRightmostChild(sibling, bNoBlockCrossing); |
michael@0 | 3186 | if (!leaf) { |
michael@0 | 3187 | return sibling; |
michael@0 | 3188 | } |
michael@0 | 3189 | |
michael@0 | 3190 | return leaf; |
michael@0 | 3191 | } |
michael@0 | 3192 | |
michael@0 | 3193 | nsINode *parent = cur->GetParentNode(); |
michael@0 | 3194 | if (!parent) { |
michael@0 | 3195 | return nullptr; |
michael@0 | 3196 | } |
michael@0 | 3197 | |
michael@0 | 3198 | NS_ASSERTION(IsDescendantOfEditorRoot(parent), |
michael@0 | 3199 | "We started with a proper descendant of root, and should stop " |
michael@0 | 3200 | "if we ever hit the root, so we better have a descendant of " |
michael@0 | 3201 | "root now!"); |
michael@0 | 3202 | if (IsEditorRoot(parent) || |
michael@0 | 3203 | (bNoBlockCrossing && IsBlockNode(parent))) { |
michael@0 | 3204 | return nullptr; |
michael@0 | 3205 | } |
michael@0 | 3206 | |
michael@0 | 3207 | cur = parent; |
michael@0 | 3208 | } |
michael@0 | 3209 | |
michael@0 | 3210 | NS_NOTREACHED("What part of for(;;) do you not understand?"); |
michael@0 | 3211 | return nullptr; |
michael@0 | 3212 | } |
michael@0 | 3213 | |
michael@0 | 3214 | nsresult |
michael@0 | 3215 | nsEditor::GetNextNode(nsIDOMNode* aCurrentNode, |
michael@0 | 3216 | bool aEditableNode, |
michael@0 | 3217 | nsCOMPtr<nsIDOMNode> *aResultNode, |
michael@0 | 3218 | bool bNoBlockCrossing) |
michael@0 | 3219 | { |
michael@0 | 3220 | nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode); |
michael@0 | 3221 | if (!currentNode || !aResultNode) { |
michael@0 | 3222 | return NS_ERROR_NULL_POINTER; |
michael@0 | 3223 | } |
michael@0 | 3224 | |
michael@0 | 3225 | *aResultNode = do_QueryInterface(GetNextNode(currentNode, aEditableNode, |
michael@0 | 3226 | bNoBlockCrossing)); |
michael@0 | 3227 | return NS_OK; |
michael@0 | 3228 | } |
michael@0 | 3229 | |
michael@0 | 3230 | nsIContent* |
michael@0 | 3231 | nsEditor::GetNextNode(nsINode* aCurrentNode, |
michael@0 | 3232 | bool aEditableNode, |
michael@0 | 3233 | bool bNoBlockCrossing) |
michael@0 | 3234 | { |
michael@0 | 3235 | MOZ_ASSERT(aCurrentNode); |
michael@0 | 3236 | |
michael@0 | 3237 | if (!IsDescendantOfEditorRoot(aCurrentNode)) { |
michael@0 | 3238 | return nullptr; |
michael@0 | 3239 | } |
michael@0 | 3240 | |
michael@0 | 3241 | return FindNode(aCurrentNode, true, aEditableNode, bNoBlockCrossing); |
michael@0 | 3242 | } |
michael@0 | 3243 | |
michael@0 | 3244 | nsIContent* |
michael@0 | 3245 | nsEditor::FindNode(nsINode *aCurrentNode, |
michael@0 | 3246 | bool aGoForward, |
michael@0 | 3247 | bool aEditableNode, |
michael@0 | 3248 | bool bNoBlockCrossing) |
michael@0 | 3249 | { |
michael@0 | 3250 | if (IsEditorRoot(aCurrentNode)) { |
michael@0 | 3251 | // Don't allow traversal above the root node! This helps |
michael@0 | 3252 | // prevent us from accidentally editing browser content |
michael@0 | 3253 | // when the editor is in a text widget. |
michael@0 | 3254 | |
michael@0 | 3255 | return nullptr; |
michael@0 | 3256 | } |
michael@0 | 3257 | |
michael@0 | 3258 | nsCOMPtr<nsIContent> candidate = |
michael@0 | 3259 | FindNextLeafNode(aCurrentNode, aGoForward, bNoBlockCrossing); |
michael@0 | 3260 | |
michael@0 | 3261 | if (!candidate) { |
michael@0 | 3262 | return nullptr; |
michael@0 | 3263 | } |
michael@0 | 3264 | |
michael@0 | 3265 | if (!aEditableNode || IsEditable(candidate)) { |
michael@0 | 3266 | return candidate; |
michael@0 | 3267 | } |
michael@0 | 3268 | |
michael@0 | 3269 | return FindNode(candidate, aGoForward, aEditableNode, bNoBlockCrossing); |
michael@0 | 3270 | } |
michael@0 | 3271 | |
michael@0 | 3272 | nsIDOMNode* |
michael@0 | 3273 | nsEditor::GetRightmostChild(nsIDOMNode* aCurrentNode, |
michael@0 | 3274 | bool bNoBlockCrossing) |
michael@0 | 3275 | { |
michael@0 | 3276 | nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode); |
michael@0 | 3277 | nsIContent* result = GetRightmostChild(currentNode, bNoBlockCrossing); |
michael@0 | 3278 | return result ? result->AsDOMNode() : nullptr; |
michael@0 | 3279 | } |
michael@0 | 3280 | |
michael@0 | 3281 | nsIContent* |
michael@0 | 3282 | nsEditor::GetRightmostChild(nsINode *aCurrentNode, |
michael@0 | 3283 | bool bNoBlockCrossing) |
michael@0 | 3284 | { |
michael@0 | 3285 | NS_ENSURE_TRUE(aCurrentNode, nullptr); |
michael@0 | 3286 | nsIContent *cur = aCurrentNode->GetLastChild(); |
michael@0 | 3287 | if (!cur) { |
michael@0 | 3288 | return nullptr; |
michael@0 | 3289 | } |
michael@0 | 3290 | for (;;) { |
michael@0 | 3291 | if (bNoBlockCrossing && IsBlockNode(cur)) { |
michael@0 | 3292 | return cur; |
michael@0 | 3293 | } |
michael@0 | 3294 | nsIContent* next = cur->GetLastChild(); |
michael@0 | 3295 | if (!next) { |
michael@0 | 3296 | return cur; |
michael@0 | 3297 | } |
michael@0 | 3298 | cur = next; |
michael@0 | 3299 | } |
michael@0 | 3300 | |
michael@0 | 3301 | NS_NOTREACHED("What part of for(;;) do you not understand?"); |
michael@0 | 3302 | return nullptr; |
michael@0 | 3303 | } |
michael@0 | 3304 | |
michael@0 | 3305 | nsIContent* |
michael@0 | 3306 | nsEditor::GetLeftmostChild(nsINode *aCurrentNode, |
michael@0 | 3307 | bool bNoBlockCrossing) |
michael@0 | 3308 | { |
michael@0 | 3309 | NS_ENSURE_TRUE(aCurrentNode, nullptr); |
michael@0 | 3310 | nsIContent *cur = aCurrentNode->GetFirstChild(); |
michael@0 | 3311 | if (!cur) { |
michael@0 | 3312 | return nullptr; |
michael@0 | 3313 | } |
michael@0 | 3314 | for (;;) { |
michael@0 | 3315 | if (bNoBlockCrossing && IsBlockNode(cur)) { |
michael@0 | 3316 | return cur; |
michael@0 | 3317 | } |
michael@0 | 3318 | nsIContent *next = cur->GetFirstChild(); |
michael@0 | 3319 | if (!next) { |
michael@0 | 3320 | return cur; |
michael@0 | 3321 | } |
michael@0 | 3322 | cur = next; |
michael@0 | 3323 | } |
michael@0 | 3324 | |
michael@0 | 3325 | NS_NOTREACHED("What part of for(;;) do you not understand?"); |
michael@0 | 3326 | return nullptr; |
michael@0 | 3327 | } |
michael@0 | 3328 | |
michael@0 | 3329 | nsIDOMNode* |
michael@0 | 3330 | nsEditor::GetLeftmostChild(nsIDOMNode* aCurrentNode, |
michael@0 | 3331 | bool bNoBlockCrossing) |
michael@0 | 3332 | { |
michael@0 | 3333 | nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode); |
michael@0 | 3334 | nsIContent* result = GetLeftmostChild(currentNode, bNoBlockCrossing); |
michael@0 | 3335 | return result ? result->AsDOMNode() : nullptr; |
michael@0 | 3336 | } |
michael@0 | 3337 | |
michael@0 | 3338 | bool |
michael@0 | 3339 | nsEditor::IsBlockNode(nsIDOMNode* aNode) |
michael@0 | 3340 | { |
michael@0 | 3341 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 3342 | return IsBlockNode(node); |
michael@0 | 3343 | } |
michael@0 | 3344 | |
michael@0 | 3345 | bool |
michael@0 | 3346 | nsEditor::IsBlockNode(nsINode* aNode) |
michael@0 | 3347 | { |
michael@0 | 3348 | // stub to be overridden in nsHTMLEditor. |
michael@0 | 3349 | // screwing around with the class hierarchy here in order |
michael@0 | 3350 | // to not duplicate the code in GetNextNode/GetPrevNode |
michael@0 | 3351 | // across both nsEditor/nsHTMLEditor. |
michael@0 | 3352 | return false; |
michael@0 | 3353 | } |
michael@0 | 3354 | |
michael@0 | 3355 | bool |
michael@0 | 3356 | nsEditor::CanContain(nsIDOMNode* aParent, nsIDOMNode* aChild) |
michael@0 | 3357 | { |
michael@0 | 3358 | nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent); |
michael@0 | 3359 | NS_ENSURE_TRUE(parent, false); |
michael@0 | 3360 | |
michael@0 | 3361 | switch (parent->NodeType()) { |
michael@0 | 3362 | case nsIDOMNode::ELEMENT_NODE: |
michael@0 | 3363 | case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: |
michael@0 | 3364 | return TagCanContain(parent->Tag(), aChild); |
michael@0 | 3365 | } |
michael@0 | 3366 | return false; |
michael@0 | 3367 | } |
michael@0 | 3368 | |
michael@0 | 3369 | bool |
michael@0 | 3370 | nsEditor::CanContainTag(nsIDOMNode* aParent, nsIAtom* aChildTag) |
michael@0 | 3371 | { |
michael@0 | 3372 | nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent); |
michael@0 | 3373 | NS_ENSURE_TRUE(parent, false); |
michael@0 | 3374 | |
michael@0 | 3375 | switch (parent->NodeType()) { |
michael@0 | 3376 | case nsIDOMNode::ELEMENT_NODE: |
michael@0 | 3377 | case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: |
michael@0 | 3378 | return TagCanContainTag(parent->Tag(), aChildTag); |
michael@0 | 3379 | } |
michael@0 | 3380 | return false; |
michael@0 | 3381 | } |
michael@0 | 3382 | |
michael@0 | 3383 | bool |
michael@0 | 3384 | nsEditor::TagCanContain(nsIAtom* aParentTag, nsIDOMNode* aChild) |
michael@0 | 3385 | { |
michael@0 | 3386 | nsCOMPtr<nsIContent> child = do_QueryInterface(aChild); |
michael@0 | 3387 | NS_ENSURE_TRUE(child, false); |
michael@0 | 3388 | |
michael@0 | 3389 | switch (child->NodeType()) { |
michael@0 | 3390 | case nsIDOMNode::TEXT_NODE: |
michael@0 | 3391 | case nsIDOMNode::ELEMENT_NODE: |
michael@0 | 3392 | case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: |
michael@0 | 3393 | return TagCanContainTag(aParentTag, child->Tag()); |
michael@0 | 3394 | } |
michael@0 | 3395 | return false; |
michael@0 | 3396 | } |
michael@0 | 3397 | |
michael@0 | 3398 | bool |
michael@0 | 3399 | nsEditor::TagCanContainTag(nsIAtom* aParentTag, nsIAtom* aChildTag) |
michael@0 | 3400 | { |
michael@0 | 3401 | return true; |
michael@0 | 3402 | } |
michael@0 | 3403 | |
michael@0 | 3404 | bool |
michael@0 | 3405 | nsEditor::IsRoot(nsIDOMNode* inNode) |
michael@0 | 3406 | { |
michael@0 | 3407 | NS_ENSURE_TRUE(inNode, false); |
michael@0 | 3408 | |
michael@0 | 3409 | nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(GetRoot()); |
michael@0 | 3410 | |
michael@0 | 3411 | return inNode == rootNode; |
michael@0 | 3412 | } |
michael@0 | 3413 | |
michael@0 | 3414 | bool |
michael@0 | 3415 | nsEditor::IsRoot(nsINode* inNode) |
michael@0 | 3416 | { |
michael@0 | 3417 | NS_ENSURE_TRUE(inNode, false); |
michael@0 | 3418 | |
michael@0 | 3419 | nsCOMPtr<nsINode> rootNode = GetRoot(); |
michael@0 | 3420 | |
michael@0 | 3421 | return inNode == rootNode; |
michael@0 | 3422 | } |
michael@0 | 3423 | |
michael@0 | 3424 | bool |
michael@0 | 3425 | nsEditor::IsEditorRoot(nsINode* aNode) |
michael@0 | 3426 | { |
michael@0 | 3427 | NS_ENSURE_TRUE(aNode, false); |
michael@0 | 3428 | nsCOMPtr<nsINode> rootNode = GetEditorRoot(); |
michael@0 | 3429 | return aNode == rootNode; |
michael@0 | 3430 | } |
michael@0 | 3431 | |
michael@0 | 3432 | bool |
michael@0 | 3433 | nsEditor::IsDescendantOfRoot(nsIDOMNode* inNode) |
michael@0 | 3434 | { |
michael@0 | 3435 | nsCOMPtr<nsINode> node = do_QueryInterface(inNode); |
michael@0 | 3436 | return IsDescendantOfRoot(node); |
michael@0 | 3437 | } |
michael@0 | 3438 | |
michael@0 | 3439 | bool |
michael@0 | 3440 | nsEditor::IsDescendantOfRoot(nsINode* inNode) |
michael@0 | 3441 | { |
michael@0 | 3442 | NS_ENSURE_TRUE(inNode, false); |
michael@0 | 3443 | nsCOMPtr<nsIContent> root = GetRoot(); |
michael@0 | 3444 | NS_ENSURE_TRUE(root, false); |
michael@0 | 3445 | |
michael@0 | 3446 | return nsContentUtils::ContentIsDescendantOf(inNode, root); |
michael@0 | 3447 | } |
michael@0 | 3448 | |
michael@0 | 3449 | bool |
michael@0 | 3450 | nsEditor::IsDescendantOfEditorRoot(nsIDOMNode* aNode) |
michael@0 | 3451 | { |
michael@0 | 3452 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 3453 | return IsDescendantOfEditorRoot(node); |
michael@0 | 3454 | } |
michael@0 | 3455 | |
michael@0 | 3456 | bool |
michael@0 | 3457 | nsEditor::IsDescendantOfEditorRoot(nsINode* aNode) |
michael@0 | 3458 | { |
michael@0 | 3459 | NS_ENSURE_TRUE(aNode, false); |
michael@0 | 3460 | nsCOMPtr<nsIContent> root = GetEditorRoot(); |
michael@0 | 3461 | NS_ENSURE_TRUE(root, false); |
michael@0 | 3462 | |
michael@0 | 3463 | return nsContentUtils::ContentIsDescendantOf(aNode, root); |
michael@0 | 3464 | } |
michael@0 | 3465 | |
michael@0 | 3466 | bool |
michael@0 | 3467 | nsEditor::IsContainer(nsIDOMNode *aNode) |
michael@0 | 3468 | { |
michael@0 | 3469 | return aNode ? true : false; |
michael@0 | 3470 | } |
michael@0 | 3471 | |
michael@0 | 3472 | static inline bool |
michael@0 | 3473 | IsElementVisible(dom::Element* aElement) |
michael@0 | 3474 | { |
michael@0 | 3475 | if (aElement->GetPrimaryFrame()) { |
michael@0 | 3476 | // It's visible, for our purposes |
michael@0 | 3477 | return true; |
michael@0 | 3478 | } |
michael@0 | 3479 | |
michael@0 | 3480 | nsIContent *cur = aElement; |
michael@0 | 3481 | for (; ;) { |
michael@0 | 3482 | // Walk up the tree looking for the nearest ancestor with a frame. |
michael@0 | 3483 | // The state of the child right below it will determine whether |
michael@0 | 3484 | // we might possibly have a frame or not. |
michael@0 | 3485 | bool haveLazyBitOnChild = cur->HasFlag(NODE_NEEDS_FRAME); |
michael@0 | 3486 | cur = cur->GetFlattenedTreeParent(); |
michael@0 | 3487 | if (!cur) { |
michael@0 | 3488 | if (!haveLazyBitOnChild) { |
michael@0 | 3489 | // None of our ancestors have lazy bits set, so we shouldn't |
michael@0 | 3490 | // have a frame |
michael@0 | 3491 | return false; |
michael@0 | 3492 | } |
michael@0 | 3493 | |
michael@0 | 3494 | // The root has a lazy frame construction bit. We need to check |
michael@0 | 3495 | // our style. |
michael@0 | 3496 | break; |
michael@0 | 3497 | } |
michael@0 | 3498 | |
michael@0 | 3499 | if (cur->GetPrimaryFrame()) { |
michael@0 | 3500 | if (!haveLazyBitOnChild) { |
michael@0 | 3501 | // Our ancestor directly under |cur| doesn't have lazy bits; |
michael@0 | 3502 | // that means we won't get a frame |
michael@0 | 3503 | return false; |
michael@0 | 3504 | } |
michael@0 | 3505 | |
michael@0 | 3506 | if (cur->GetPrimaryFrame()->IsLeaf()) { |
michael@0 | 3507 | // Nothing under here will ever get frames |
michael@0 | 3508 | return false; |
michael@0 | 3509 | } |
michael@0 | 3510 | |
michael@0 | 3511 | // Otherwise, we might end up with a frame when that lazy bit is |
michael@0 | 3512 | // processed. Figure out our actual style. |
michael@0 | 3513 | break; |
michael@0 | 3514 | } |
michael@0 | 3515 | } |
michael@0 | 3516 | |
michael@0 | 3517 | // Now it might be that we have no frame because we're in a |
michael@0 | 3518 | // display:none subtree, or it might be that we're just dealing with |
michael@0 | 3519 | // lazy frame construction and it hasn't happened yet. Check which |
michael@0 | 3520 | // one it is. |
michael@0 | 3521 | nsRefPtr<nsStyleContext> styleContext = |
michael@0 | 3522 | nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, |
michael@0 | 3523 | nullptr, nullptr); |
michael@0 | 3524 | if (styleContext) { |
michael@0 | 3525 | return styleContext->StyleDisplay()->mDisplay != NS_STYLE_DISPLAY_NONE; |
michael@0 | 3526 | } |
michael@0 | 3527 | return false; |
michael@0 | 3528 | } |
michael@0 | 3529 | |
michael@0 | 3530 | bool |
michael@0 | 3531 | nsEditor::IsEditable(nsIDOMNode *aNode) |
michael@0 | 3532 | { |
michael@0 | 3533 | nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
michael@0 | 3534 | return IsEditable(content); |
michael@0 | 3535 | } |
michael@0 | 3536 | |
michael@0 | 3537 | bool |
michael@0 | 3538 | nsEditor::IsEditable(nsIContent *aNode) |
michael@0 | 3539 | { |
michael@0 | 3540 | NS_ENSURE_TRUE(aNode, false); |
michael@0 | 3541 | |
michael@0 | 3542 | if (IsMozEditorBogusNode(aNode) || !IsModifiableNode(aNode)) return false; |
michael@0 | 3543 | |
michael@0 | 3544 | // see if it has a frame. If so, we'll edit it. |
michael@0 | 3545 | // special case for textnodes: frame must have width. |
michael@0 | 3546 | if (aNode->IsElement() && !IsElementVisible(aNode->AsElement())) { |
michael@0 | 3547 | // If the element has no frame, it's not editable. Note that we |
michael@0 | 3548 | // need to check IsElement() here, because some of our tests |
michael@0 | 3549 | // rely on frameless textnodes being visible. |
michael@0 | 3550 | return false; |
michael@0 | 3551 | } |
michael@0 | 3552 | switch (aNode->NodeType()) { |
michael@0 | 3553 | case nsIDOMNode::ELEMENT_NODE: |
michael@0 | 3554 | case nsIDOMNode::TEXT_NODE: |
michael@0 | 3555 | return true; // element or text node; not invisible |
michael@0 | 3556 | default: |
michael@0 | 3557 | return false; |
michael@0 | 3558 | } |
michael@0 | 3559 | } |
michael@0 | 3560 | |
michael@0 | 3561 | bool |
michael@0 | 3562 | nsEditor::IsMozEditorBogusNode(nsIContent *element) |
michael@0 | 3563 | { |
michael@0 | 3564 | return element && |
michael@0 | 3565 | element->AttrValueIs(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, |
michael@0 | 3566 | kMOZEditorBogusNodeValue, eCaseMatters); |
michael@0 | 3567 | } |
michael@0 | 3568 | |
michael@0 | 3569 | uint32_t |
michael@0 | 3570 | nsEditor::CountEditableChildren(nsINode* aNode) |
michael@0 | 3571 | { |
michael@0 | 3572 | MOZ_ASSERT(aNode); |
michael@0 | 3573 | uint32_t count = 0; |
michael@0 | 3574 | for (nsIContent* child = aNode->GetFirstChild(); |
michael@0 | 3575 | child; |
michael@0 | 3576 | child = child->GetNextSibling()) { |
michael@0 | 3577 | if (IsEditable(child)) { |
michael@0 | 3578 | ++count; |
michael@0 | 3579 | } |
michael@0 | 3580 | } |
michael@0 | 3581 | return count; |
michael@0 | 3582 | } |
michael@0 | 3583 | |
michael@0 | 3584 | //END nsEditor static utility methods |
michael@0 | 3585 | |
michael@0 | 3586 | |
michael@0 | 3587 | NS_IMETHODIMP nsEditor::IncrementModificationCount(int32_t inNumMods) |
michael@0 | 3588 | { |
michael@0 | 3589 | uint32_t oldModCount = mModCount; |
michael@0 | 3590 | |
michael@0 | 3591 | mModCount += inNumMods; |
michael@0 | 3592 | |
michael@0 | 3593 | if ((oldModCount == 0 && mModCount != 0) |
michael@0 | 3594 | || (oldModCount != 0 && mModCount == 0)) |
michael@0 | 3595 | NotifyDocumentListeners(eDocumentStateChanged); |
michael@0 | 3596 | return NS_OK; |
michael@0 | 3597 | } |
michael@0 | 3598 | |
michael@0 | 3599 | |
michael@0 | 3600 | NS_IMETHODIMP nsEditor::GetModificationCount(int32_t *outModCount) |
michael@0 | 3601 | { |
michael@0 | 3602 | NS_ENSURE_ARG_POINTER(outModCount); |
michael@0 | 3603 | *outModCount = mModCount; |
michael@0 | 3604 | return NS_OK; |
michael@0 | 3605 | } |
michael@0 | 3606 | |
michael@0 | 3607 | |
michael@0 | 3608 | NS_IMETHODIMP nsEditor::ResetModificationCount() |
michael@0 | 3609 | { |
michael@0 | 3610 | bool doNotify = (mModCount != 0); |
michael@0 | 3611 | |
michael@0 | 3612 | mModCount = 0; |
michael@0 | 3613 | |
michael@0 | 3614 | if (doNotify) |
michael@0 | 3615 | NotifyDocumentListeners(eDocumentStateChanged); |
michael@0 | 3616 | return NS_OK; |
michael@0 | 3617 | } |
michael@0 | 3618 | |
michael@0 | 3619 | //END nsEditor Private methods |
michael@0 | 3620 | |
michael@0 | 3621 | |
michael@0 | 3622 | |
michael@0 | 3623 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3624 | // GetTag: digs out the atom for the tag of this node |
michael@0 | 3625 | // |
michael@0 | 3626 | nsIAtom * |
michael@0 | 3627 | nsEditor::GetTag(nsIDOMNode *aNode) |
michael@0 | 3628 | { |
michael@0 | 3629 | nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
michael@0 | 3630 | |
michael@0 | 3631 | if (!content) |
michael@0 | 3632 | { |
michael@0 | 3633 | NS_ASSERTION(aNode, "null node passed to nsEditor::Tag()"); |
michael@0 | 3634 | |
michael@0 | 3635 | return nullptr; |
michael@0 | 3636 | } |
michael@0 | 3637 | |
michael@0 | 3638 | return content->Tag(); |
michael@0 | 3639 | } |
michael@0 | 3640 | |
michael@0 | 3641 | |
michael@0 | 3642 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3643 | // GetTagString: digs out string for the tag of this node |
michael@0 | 3644 | // |
michael@0 | 3645 | nsresult |
michael@0 | 3646 | nsEditor::GetTagString(nsIDOMNode *aNode, nsAString& outString) |
michael@0 | 3647 | { |
michael@0 | 3648 | if (!aNode) |
michael@0 | 3649 | { |
michael@0 | 3650 | NS_NOTREACHED("null node passed to nsEditor::GetTag()"); |
michael@0 | 3651 | return NS_ERROR_NULL_POINTER; |
michael@0 | 3652 | } |
michael@0 | 3653 | |
michael@0 | 3654 | nsIAtom *atom = GetTag(aNode); |
michael@0 | 3655 | if (!atom) |
michael@0 | 3656 | { |
michael@0 | 3657 | return NS_ERROR_FAILURE; |
michael@0 | 3658 | } |
michael@0 | 3659 | |
michael@0 | 3660 | atom->ToString(outString); |
michael@0 | 3661 | return NS_OK; |
michael@0 | 3662 | } |
michael@0 | 3663 | |
michael@0 | 3664 | |
michael@0 | 3665 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3666 | // NodesSameType: do these nodes have the same tag? |
michael@0 | 3667 | // |
michael@0 | 3668 | bool |
michael@0 | 3669 | nsEditor::NodesSameType(nsIDOMNode *aNode1, nsIDOMNode *aNode2) |
michael@0 | 3670 | { |
michael@0 | 3671 | if (!aNode1 || !aNode2) { |
michael@0 | 3672 | NS_NOTREACHED("null node passed to nsEditor::NodesSameType()"); |
michael@0 | 3673 | return false; |
michael@0 | 3674 | } |
michael@0 | 3675 | |
michael@0 | 3676 | nsCOMPtr<nsIContent> content1 = do_QueryInterface(aNode1); |
michael@0 | 3677 | NS_ENSURE_TRUE(content1, false); |
michael@0 | 3678 | |
michael@0 | 3679 | nsCOMPtr<nsIContent> content2 = do_QueryInterface(aNode2); |
michael@0 | 3680 | NS_ENSURE_TRUE(content2, false); |
michael@0 | 3681 | |
michael@0 | 3682 | return AreNodesSameType(content1, content2); |
michael@0 | 3683 | } |
michael@0 | 3684 | |
michael@0 | 3685 | /* virtual */ |
michael@0 | 3686 | bool |
michael@0 | 3687 | nsEditor::AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2) |
michael@0 | 3688 | { |
michael@0 | 3689 | MOZ_ASSERT(aNode1); |
michael@0 | 3690 | MOZ_ASSERT(aNode2); |
michael@0 | 3691 | return aNode1->Tag() == aNode2->Tag(); |
michael@0 | 3692 | } |
michael@0 | 3693 | |
michael@0 | 3694 | |
michael@0 | 3695 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3696 | // IsTextNode: true if node of dom type text |
michael@0 | 3697 | // |
michael@0 | 3698 | bool |
michael@0 | 3699 | nsEditor::IsTextNode(nsIDOMNode *aNode) |
michael@0 | 3700 | { |
michael@0 | 3701 | if (!aNode) |
michael@0 | 3702 | { |
michael@0 | 3703 | NS_NOTREACHED("null node passed to IsTextNode()"); |
michael@0 | 3704 | return false; |
michael@0 | 3705 | } |
michael@0 | 3706 | |
michael@0 | 3707 | uint16_t nodeType; |
michael@0 | 3708 | aNode->GetNodeType(&nodeType); |
michael@0 | 3709 | return (nodeType == nsIDOMNode::TEXT_NODE); |
michael@0 | 3710 | } |
michael@0 | 3711 | |
michael@0 | 3712 | bool |
michael@0 | 3713 | nsEditor::IsTextNode(nsINode *aNode) |
michael@0 | 3714 | { |
michael@0 | 3715 | return aNode->NodeType() == nsIDOMNode::TEXT_NODE; |
michael@0 | 3716 | } |
michael@0 | 3717 | |
michael@0 | 3718 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3719 | // GetChildAt: returns the node at this position index in the parent |
michael@0 | 3720 | // |
michael@0 | 3721 | nsCOMPtr<nsIDOMNode> |
michael@0 | 3722 | nsEditor::GetChildAt(nsIDOMNode *aParent, int32_t aOffset) |
michael@0 | 3723 | { |
michael@0 | 3724 | nsCOMPtr<nsIDOMNode> resultNode; |
michael@0 | 3725 | |
michael@0 | 3726 | nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent); |
michael@0 | 3727 | |
michael@0 | 3728 | NS_ENSURE_TRUE(parent, resultNode); |
michael@0 | 3729 | |
michael@0 | 3730 | resultNode = do_QueryInterface(parent->GetChildAt(aOffset)); |
michael@0 | 3731 | |
michael@0 | 3732 | return resultNode; |
michael@0 | 3733 | } |
michael@0 | 3734 | |
michael@0 | 3735 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3736 | // GetNodeAtRangeOffsetPoint: returns the node at this position in a range, |
michael@0 | 3737 | // assuming that aParentOrNode is the node itself if it's a text node, or |
michael@0 | 3738 | // the node's parent otherwise. |
michael@0 | 3739 | // |
michael@0 | 3740 | nsCOMPtr<nsIDOMNode> |
michael@0 | 3741 | nsEditor::GetNodeAtRangeOffsetPoint(nsIDOMNode* aParentOrNode, int32_t aOffset) |
michael@0 | 3742 | { |
michael@0 | 3743 | if (IsTextNode(aParentOrNode)) { |
michael@0 | 3744 | return aParentOrNode; |
michael@0 | 3745 | } |
michael@0 | 3746 | return GetChildAt(aParentOrNode, aOffset); |
michael@0 | 3747 | } |
michael@0 | 3748 | |
michael@0 | 3749 | |
michael@0 | 3750 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3751 | // GetStartNodeAndOffset: returns whatever the start parent & offset is of |
michael@0 | 3752 | // the first range in the selection. |
michael@0 | 3753 | nsresult |
michael@0 | 3754 | nsEditor::GetStartNodeAndOffset(nsISelection *aSelection, |
michael@0 | 3755 | nsIDOMNode **outStartNode, |
michael@0 | 3756 | int32_t *outStartOffset) |
michael@0 | 3757 | { |
michael@0 | 3758 | NS_ENSURE_TRUE(outStartNode && outStartOffset && aSelection, NS_ERROR_NULL_POINTER); |
michael@0 | 3759 | |
michael@0 | 3760 | nsCOMPtr<nsINode> startNode; |
michael@0 | 3761 | nsresult rv = GetStartNodeAndOffset(static_cast<Selection*>(aSelection), |
michael@0 | 3762 | getter_AddRefs(startNode), |
michael@0 | 3763 | outStartOffset); |
michael@0 | 3764 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3765 | |
michael@0 | 3766 | if (startNode) { |
michael@0 | 3767 | NS_ADDREF(*outStartNode = startNode->AsDOMNode()); |
michael@0 | 3768 | } else { |
michael@0 | 3769 | *outStartNode = nullptr; |
michael@0 | 3770 | } |
michael@0 | 3771 | return NS_OK; |
michael@0 | 3772 | } |
michael@0 | 3773 | |
michael@0 | 3774 | nsresult |
michael@0 | 3775 | nsEditor::GetStartNodeAndOffset(Selection* aSelection, nsINode** aStartNode, |
michael@0 | 3776 | int32_t* aStartOffset) |
michael@0 | 3777 | { |
michael@0 | 3778 | MOZ_ASSERT(aSelection); |
michael@0 | 3779 | MOZ_ASSERT(aStartNode); |
michael@0 | 3780 | MOZ_ASSERT(aStartOffset); |
michael@0 | 3781 | |
michael@0 | 3782 | *aStartNode = nullptr; |
michael@0 | 3783 | *aStartOffset = 0; |
michael@0 | 3784 | |
michael@0 | 3785 | NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE); |
michael@0 | 3786 | |
michael@0 | 3787 | const nsRange* range = aSelection->GetRangeAt(0); |
michael@0 | 3788 | NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
michael@0 | 3789 | |
michael@0 | 3790 | NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE); |
michael@0 | 3791 | |
michael@0 | 3792 | NS_IF_ADDREF(*aStartNode = range->GetStartParent()); |
michael@0 | 3793 | *aStartOffset = range->StartOffset(); |
michael@0 | 3794 | return NS_OK; |
michael@0 | 3795 | } |
michael@0 | 3796 | |
michael@0 | 3797 | |
michael@0 | 3798 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3799 | // GetEndNodeAndOffset: returns whatever the end parent & offset is of |
michael@0 | 3800 | // the first range in the selection. |
michael@0 | 3801 | nsresult |
michael@0 | 3802 | nsEditor::GetEndNodeAndOffset(nsISelection *aSelection, |
michael@0 | 3803 | nsIDOMNode **outEndNode, |
michael@0 | 3804 | int32_t *outEndOffset) |
michael@0 | 3805 | { |
michael@0 | 3806 | NS_ENSURE_TRUE(outEndNode && outEndOffset && aSelection, NS_ERROR_NULL_POINTER); |
michael@0 | 3807 | |
michael@0 | 3808 | nsCOMPtr<nsINode> endNode; |
michael@0 | 3809 | nsresult rv = GetEndNodeAndOffset(static_cast<Selection*>(aSelection), |
michael@0 | 3810 | getter_AddRefs(endNode), |
michael@0 | 3811 | outEndOffset); |
michael@0 | 3812 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3813 | |
michael@0 | 3814 | if (endNode) { |
michael@0 | 3815 | NS_ADDREF(*outEndNode = endNode->AsDOMNode()); |
michael@0 | 3816 | } else { |
michael@0 | 3817 | *outEndNode = nullptr; |
michael@0 | 3818 | } |
michael@0 | 3819 | return NS_OK; |
michael@0 | 3820 | } |
michael@0 | 3821 | |
michael@0 | 3822 | nsresult |
michael@0 | 3823 | nsEditor::GetEndNodeAndOffset(Selection* aSelection, nsINode** aEndNode, |
michael@0 | 3824 | int32_t* aEndOffset) |
michael@0 | 3825 | { |
michael@0 | 3826 | MOZ_ASSERT(aSelection); |
michael@0 | 3827 | MOZ_ASSERT(aEndNode); |
michael@0 | 3828 | MOZ_ASSERT(aEndOffset); |
michael@0 | 3829 | |
michael@0 | 3830 | *aEndNode = nullptr; |
michael@0 | 3831 | *aEndOffset = 0; |
michael@0 | 3832 | |
michael@0 | 3833 | NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE); |
michael@0 | 3834 | |
michael@0 | 3835 | const nsRange* range = aSelection->GetRangeAt(0); |
michael@0 | 3836 | NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
michael@0 | 3837 | |
michael@0 | 3838 | NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE); |
michael@0 | 3839 | |
michael@0 | 3840 | NS_IF_ADDREF(*aEndNode = range->GetEndParent()); |
michael@0 | 3841 | *aEndOffset = range->EndOffset(); |
michael@0 | 3842 | return NS_OK; |
michael@0 | 3843 | } |
michael@0 | 3844 | |
michael@0 | 3845 | |
michael@0 | 3846 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3847 | // IsPreformatted: checks the style info for the node for the preformatted |
michael@0 | 3848 | // text style. |
michael@0 | 3849 | nsresult |
michael@0 | 3850 | nsEditor::IsPreformatted(nsIDOMNode *aNode, bool *aResult) |
michael@0 | 3851 | { |
michael@0 | 3852 | nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
michael@0 | 3853 | |
michael@0 | 3854 | NS_ENSURE_TRUE(aResult && content, NS_ERROR_NULL_POINTER); |
michael@0 | 3855 | |
michael@0 | 3856 | nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
michael@0 | 3857 | NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 3858 | |
michael@0 | 3859 | // Look at the node (and its parent if it's not an element), and grab its style context |
michael@0 | 3860 | nsRefPtr<nsStyleContext> elementStyle; |
michael@0 | 3861 | if (!content->IsElement()) { |
michael@0 | 3862 | content = content->GetParent(); |
michael@0 | 3863 | } |
michael@0 | 3864 | if (content && content->IsElement()) { |
michael@0 | 3865 | elementStyle = nsComputedDOMStyle::GetStyleContextForElementNoFlush(content->AsElement(), |
michael@0 | 3866 | nullptr, |
michael@0 | 3867 | ps); |
michael@0 | 3868 | } |
michael@0 | 3869 | |
michael@0 | 3870 | if (!elementStyle) |
michael@0 | 3871 | { |
michael@0 | 3872 | // Consider nodes without a style context to be NOT preformatted: |
michael@0 | 3873 | // For instance, this is true of JS tags inside the body (which show |
michael@0 | 3874 | // up as #text nodes but have no style context). |
michael@0 | 3875 | *aResult = false; |
michael@0 | 3876 | return NS_OK; |
michael@0 | 3877 | } |
michael@0 | 3878 | |
michael@0 | 3879 | const nsStyleText* styleText = elementStyle->StyleText(); |
michael@0 | 3880 | |
michael@0 | 3881 | *aResult = styleText->WhiteSpaceIsSignificant(); |
michael@0 | 3882 | return NS_OK; |
michael@0 | 3883 | } |
michael@0 | 3884 | |
michael@0 | 3885 | |
michael@0 | 3886 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3887 | // SplitNodeDeep: this splits a node "deeply", splitting children as |
michael@0 | 3888 | // appropriate. The place to split is represented by |
michael@0 | 3889 | // a dom point at {splitPointParent, splitPointOffset}. |
michael@0 | 3890 | // That dom point must be inside aNode, which is the node to |
michael@0 | 3891 | // split. outOffset is set to the offset in the parent of aNode where |
michael@0 | 3892 | // the split terminates - where you would want to insert |
michael@0 | 3893 | // a new element, for instance, if that's why you were splitting |
michael@0 | 3894 | // the node. |
michael@0 | 3895 | // |
michael@0 | 3896 | nsresult |
michael@0 | 3897 | nsEditor::SplitNodeDeep(nsIDOMNode *aNode, |
michael@0 | 3898 | nsIDOMNode *aSplitPointParent, |
michael@0 | 3899 | int32_t aSplitPointOffset, |
michael@0 | 3900 | int32_t *outOffset, |
michael@0 | 3901 | bool aNoEmptyContainers, |
michael@0 | 3902 | nsCOMPtr<nsIDOMNode> *outLeftNode, |
michael@0 | 3903 | nsCOMPtr<nsIDOMNode> *outRightNode) |
michael@0 | 3904 | { |
michael@0 | 3905 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 3906 | NS_ENSURE_TRUE(node && aSplitPointParent && outOffset, NS_ERROR_NULL_POINTER); |
michael@0 | 3907 | int32_t offset = aSplitPointOffset; |
michael@0 | 3908 | |
michael@0 | 3909 | if (outLeftNode) *outLeftNode = nullptr; |
michael@0 | 3910 | if (outRightNode) *outRightNode = nullptr; |
michael@0 | 3911 | |
michael@0 | 3912 | nsCOMPtr<nsINode> nodeToSplit = do_QueryInterface(aSplitPointParent); |
michael@0 | 3913 | while (nodeToSplit) { |
michael@0 | 3914 | // need to insert rules code call here to do things like |
michael@0 | 3915 | // not split a list if you are after the last <li> or before the first, etc. |
michael@0 | 3916 | // for now we just have some smarts about unneccessarily splitting |
michael@0 | 3917 | // textnodes, which should be universal enough to put straight in |
michael@0 | 3918 | // this nsEditor routine. |
michael@0 | 3919 | |
michael@0 | 3920 | nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(nodeToSplit); |
michael@0 | 3921 | uint32_t len = nodeToSplit->Length(); |
michael@0 | 3922 | bool bDoSplit = false; |
michael@0 | 3923 | |
michael@0 | 3924 | if (!(aNoEmptyContainers || nodeAsText) || (offset && (offset != (int32_t)len))) |
michael@0 | 3925 | { |
michael@0 | 3926 | bDoSplit = true; |
michael@0 | 3927 | nsCOMPtr<nsIDOMNode> tempNode; |
michael@0 | 3928 | nsresult rv = SplitNode(nodeToSplit->AsDOMNode(), offset, |
michael@0 | 3929 | getter_AddRefs(tempNode)); |
michael@0 | 3930 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 3931 | |
michael@0 | 3932 | if (outRightNode) { |
michael@0 | 3933 | *outRightNode = nodeToSplit->AsDOMNode(); |
michael@0 | 3934 | } |
michael@0 | 3935 | if (outLeftNode) { |
michael@0 | 3936 | *outLeftNode = tempNode; |
michael@0 | 3937 | } |
michael@0 | 3938 | } |
michael@0 | 3939 | |
michael@0 | 3940 | nsINode* parentNode = nodeToSplit->GetParentNode(); |
michael@0 | 3941 | NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE); |
michael@0 | 3942 | |
michael@0 | 3943 | if (!bDoSplit && offset) { |
michael@0 | 3944 | // must be "end of text node" case, we didn't split it, just move past it |
michael@0 | 3945 | offset = parentNode->IndexOf(nodeToSplit) + 1; |
michael@0 | 3946 | if (outLeftNode) { |
michael@0 | 3947 | *outLeftNode = nodeToSplit->AsDOMNode(); |
michael@0 | 3948 | } |
michael@0 | 3949 | } else { |
michael@0 | 3950 | offset = parentNode->IndexOf(nodeToSplit); |
michael@0 | 3951 | if (outRightNode) { |
michael@0 | 3952 | *outRightNode = nodeToSplit->AsDOMNode(); |
michael@0 | 3953 | } |
michael@0 | 3954 | } |
michael@0 | 3955 | |
michael@0 | 3956 | if (nodeToSplit == node) { |
michael@0 | 3957 | // we split all the way up to (and including) aNode; we're done |
michael@0 | 3958 | break; |
michael@0 | 3959 | } |
michael@0 | 3960 | |
michael@0 | 3961 | nodeToSplit = parentNode; |
michael@0 | 3962 | } |
michael@0 | 3963 | |
michael@0 | 3964 | if (!nodeToSplit) { |
michael@0 | 3965 | NS_NOTREACHED("null node obtained in nsEditor::SplitNodeDeep()"); |
michael@0 | 3966 | return NS_ERROR_FAILURE; |
michael@0 | 3967 | } |
michael@0 | 3968 | |
michael@0 | 3969 | *outOffset = offset; |
michael@0 | 3970 | return NS_OK; |
michael@0 | 3971 | } |
michael@0 | 3972 | |
michael@0 | 3973 | |
michael@0 | 3974 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 3975 | // JoinNodeDeep: this joins two like nodes "deeply", joining children as |
michael@0 | 3976 | // appropriate. |
michael@0 | 3977 | nsresult |
michael@0 | 3978 | nsEditor::JoinNodeDeep(nsIDOMNode *aLeftNode, |
michael@0 | 3979 | nsIDOMNode *aRightNode, |
michael@0 | 3980 | nsCOMPtr<nsIDOMNode> *aOutJoinNode, |
michael@0 | 3981 | int32_t *outOffset) |
michael@0 | 3982 | { |
michael@0 | 3983 | NS_ENSURE_TRUE(aLeftNode && aRightNode && aOutJoinNode && outOffset, NS_ERROR_NULL_POINTER); |
michael@0 | 3984 | |
michael@0 | 3985 | // while the rightmost children and their descendants of the left node |
michael@0 | 3986 | // match the leftmost children and their descendants of the right node |
michael@0 | 3987 | // join them up. Can you say that three times fast? |
michael@0 | 3988 | |
michael@0 | 3989 | nsCOMPtr<nsIDOMNode> leftNodeToJoin = do_QueryInterface(aLeftNode); |
michael@0 | 3990 | nsCOMPtr<nsIDOMNode> rightNodeToJoin = do_QueryInterface(aRightNode); |
michael@0 | 3991 | nsCOMPtr<nsIDOMNode> parentNode,tmp; |
michael@0 | 3992 | nsresult res = NS_OK; |
michael@0 | 3993 | |
michael@0 | 3994 | rightNodeToJoin->GetParentNode(getter_AddRefs(parentNode)); |
michael@0 | 3995 | |
michael@0 | 3996 | while (leftNodeToJoin && rightNodeToJoin && parentNode && |
michael@0 | 3997 | NodesSameType(leftNodeToJoin, rightNodeToJoin)) |
michael@0 | 3998 | { |
michael@0 | 3999 | // adjust out params |
michael@0 | 4000 | uint32_t length; |
michael@0 | 4001 | res = GetLengthOfDOMNode(leftNodeToJoin, length); |
michael@0 | 4002 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4003 | |
michael@0 | 4004 | *aOutJoinNode = rightNodeToJoin; |
michael@0 | 4005 | *outOffset = length; |
michael@0 | 4006 | |
michael@0 | 4007 | // do the join |
michael@0 | 4008 | res = JoinNodes(leftNodeToJoin, rightNodeToJoin, parentNode); |
michael@0 | 4009 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4010 | |
michael@0 | 4011 | if (IsTextNode(parentNode)) // we've joined all the way down to text nodes, we're done! |
michael@0 | 4012 | return NS_OK; |
michael@0 | 4013 | |
michael@0 | 4014 | else |
michael@0 | 4015 | { |
michael@0 | 4016 | // get new left and right nodes, and begin anew |
michael@0 | 4017 | parentNode = rightNodeToJoin; |
michael@0 | 4018 | leftNodeToJoin = GetChildAt(parentNode, length-1); |
michael@0 | 4019 | rightNodeToJoin = GetChildAt(parentNode, length); |
michael@0 | 4020 | |
michael@0 | 4021 | // skip over non-editable nodes |
michael@0 | 4022 | while (leftNodeToJoin && !IsEditable(leftNodeToJoin)) |
michael@0 | 4023 | { |
michael@0 | 4024 | leftNodeToJoin->GetPreviousSibling(getter_AddRefs(tmp)); |
michael@0 | 4025 | leftNodeToJoin = tmp; |
michael@0 | 4026 | } |
michael@0 | 4027 | if (!leftNodeToJoin) break; |
michael@0 | 4028 | |
michael@0 | 4029 | while (rightNodeToJoin && !IsEditable(rightNodeToJoin)) |
michael@0 | 4030 | { |
michael@0 | 4031 | rightNodeToJoin->GetNextSibling(getter_AddRefs(tmp)); |
michael@0 | 4032 | rightNodeToJoin = tmp; |
michael@0 | 4033 | } |
michael@0 | 4034 | if (!rightNodeToJoin) break; |
michael@0 | 4035 | } |
michael@0 | 4036 | } |
michael@0 | 4037 | |
michael@0 | 4038 | return res; |
michael@0 | 4039 | } |
michael@0 | 4040 | |
michael@0 | 4041 | void |
michael@0 | 4042 | nsEditor::BeginUpdateViewBatch() |
michael@0 | 4043 | { |
michael@0 | 4044 | NS_PRECONDITION(mUpdateCount >= 0, "bad state"); |
michael@0 | 4045 | |
michael@0 | 4046 | if (0 == mUpdateCount) |
michael@0 | 4047 | { |
michael@0 | 4048 | // Turn off selection updates and notifications. |
michael@0 | 4049 | |
michael@0 | 4050 | nsCOMPtr<nsISelection> selection; |
michael@0 | 4051 | GetSelection(getter_AddRefs(selection)); |
michael@0 | 4052 | |
michael@0 | 4053 | if (selection) |
michael@0 | 4054 | { |
michael@0 | 4055 | nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection)); |
michael@0 | 4056 | selPrivate->StartBatchChanges(); |
michael@0 | 4057 | } |
michael@0 | 4058 | } |
michael@0 | 4059 | |
michael@0 | 4060 | mUpdateCount++; |
michael@0 | 4061 | } |
michael@0 | 4062 | |
michael@0 | 4063 | |
michael@0 | 4064 | nsresult nsEditor::EndUpdateViewBatch() |
michael@0 | 4065 | { |
michael@0 | 4066 | NS_PRECONDITION(mUpdateCount > 0, "bad state"); |
michael@0 | 4067 | |
michael@0 | 4068 | if (mUpdateCount <= 0) |
michael@0 | 4069 | { |
michael@0 | 4070 | mUpdateCount = 0; |
michael@0 | 4071 | return NS_ERROR_FAILURE; |
michael@0 | 4072 | } |
michael@0 | 4073 | |
michael@0 | 4074 | mUpdateCount--; |
michael@0 | 4075 | |
michael@0 | 4076 | if (0 == mUpdateCount) |
michael@0 | 4077 | { |
michael@0 | 4078 | // Turn selection updating and notifications back on. |
michael@0 | 4079 | |
michael@0 | 4080 | nsCOMPtr<nsISelection>selection; |
michael@0 | 4081 | GetSelection(getter_AddRefs(selection)); |
michael@0 | 4082 | |
michael@0 | 4083 | if (selection) { |
michael@0 | 4084 | nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection)); |
michael@0 | 4085 | selPrivate->EndBatchChanges(); |
michael@0 | 4086 | } |
michael@0 | 4087 | } |
michael@0 | 4088 | |
michael@0 | 4089 | return NS_OK; |
michael@0 | 4090 | } |
michael@0 | 4091 | |
michael@0 | 4092 | bool |
michael@0 | 4093 | nsEditor::GetShouldTxnSetSelection() |
michael@0 | 4094 | { |
michael@0 | 4095 | return mShouldTxnSetSelection; |
michael@0 | 4096 | } |
michael@0 | 4097 | |
michael@0 | 4098 | |
michael@0 | 4099 | NS_IMETHODIMP |
michael@0 | 4100 | nsEditor::DeleteSelectionImpl(EDirection aAction, |
michael@0 | 4101 | EStripWrappers aStripWrappers) |
michael@0 | 4102 | { |
michael@0 | 4103 | MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
michael@0 | 4104 | |
michael@0 | 4105 | nsCOMPtr<nsISelection>selection; |
michael@0 | 4106 | nsresult res = GetSelection(getter_AddRefs(selection)); |
michael@0 | 4107 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4108 | nsRefPtr<EditAggregateTxn> txn; |
michael@0 | 4109 | nsCOMPtr<nsINode> deleteNode; |
michael@0 | 4110 | int32_t deleteCharOffset = 0, deleteCharLength = 0; |
michael@0 | 4111 | res = CreateTxnForDeleteSelection(aAction, getter_AddRefs(txn), |
michael@0 | 4112 | getter_AddRefs(deleteNode), |
michael@0 | 4113 | &deleteCharOffset, &deleteCharLength); |
michael@0 | 4114 | nsCOMPtr<nsIDOMCharacterData> deleteCharData(do_QueryInterface(deleteNode)); |
michael@0 | 4115 | |
michael@0 | 4116 | if (NS_SUCCEEDED(res)) |
michael@0 | 4117 | { |
michael@0 | 4118 | nsAutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); |
michael@0 | 4119 | int32_t i; |
michael@0 | 4120 | // Notify nsIEditActionListener::WillDelete[Selection|Text|Node] |
michael@0 | 4121 | if (!deleteNode) |
michael@0 | 4122 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4123 | mActionListeners[i]->WillDeleteSelection(selection); |
michael@0 | 4124 | else if (deleteCharData) |
michael@0 | 4125 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4126 | mActionListeners[i]->WillDeleteText(deleteCharData, deleteCharOffset, 1); |
michael@0 | 4127 | else |
michael@0 | 4128 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4129 | mActionListeners[i]->WillDeleteNode(deleteNode->AsDOMNode()); |
michael@0 | 4130 | |
michael@0 | 4131 | // Delete the specified amount |
michael@0 | 4132 | res = DoTransaction(txn); |
michael@0 | 4133 | |
michael@0 | 4134 | // Notify nsIEditActionListener::DidDelete[Selection|Text|Node] |
michael@0 | 4135 | if (!deleteNode) |
michael@0 | 4136 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4137 | mActionListeners[i]->DidDeleteSelection(selection); |
michael@0 | 4138 | else if (deleteCharData) |
michael@0 | 4139 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4140 | mActionListeners[i]->DidDeleteText(deleteCharData, deleteCharOffset, 1, res); |
michael@0 | 4141 | else |
michael@0 | 4142 | for (i = 0; i < mActionListeners.Count(); i++) |
michael@0 | 4143 | mActionListeners[i]->DidDeleteNode(deleteNode->AsDOMNode(), res); |
michael@0 | 4144 | } |
michael@0 | 4145 | |
michael@0 | 4146 | return res; |
michael@0 | 4147 | } |
michael@0 | 4148 | |
michael@0 | 4149 | // XXX: error handling in this routine needs to be cleaned up! |
michael@0 | 4150 | NS_IMETHODIMP |
michael@0 | 4151 | nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag, |
michael@0 | 4152 | nsIDOMNode ** aNewNode) |
michael@0 | 4153 | { |
michael@0 | 4154 | nsresult result = DeleteSelectionAndPrepareToCreateNode(); |
michael@0 | 4155 | NS_ENSURE_SUCCESS(result, result); |
michael@0 | 4156 | |
michael@0 | 4157 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 4158 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 4159 | |
michael@0 | 4160 | nsCOMPtr<nsINode> node = selection->GetAnchorNode(); |
michael@0 | 4161 | uint32_t offset = selection->AnchorOffset(); |
michael@0 | 4162 | |
michael@0 | 4163 | nsCOMPtr<nsIDOMNode> newNode; |
michael@0 | 4164 | result = CreateNode(aTag, node->AsDOMNode(), offset, |
michael@0 | 4165 | getter_AddRefs(newNode)); |
michael@0 | 4166 | // XXX: ERROR_HANDLING check result, and make sure aNewNode is set correctly |
michael@0 | 4167 | // in success/failure cases |
michael@0 | 4168 | *aNewNode = newNode; |
michael@0 | 4169 | NS_IF_ADDREF(*aNewNode); |
michael@0 | 4170 | |
michael@0 | 4171 | // we want the selection to be just after the new node |
michael@0 | 4172 | return selection->Collapse(node, offset + 1); |
michael@0 | 4173 | } |
michael@0 | 4174 | |
michael@0 | 4175 | |
michael@0 | 4176 | /* Non-interface, protected methods */ |
michael@0 | 4177 | |
michael@0 | 4178 | TextComposition* |
michael@0 | 4179 | nsEditor::GetComposition() const |
michael@0 | 4180 | { |
michael@0 | 4181 | return mComposition; |
michael@0 | 4182 | } |
michael@0 | 4183 | |
michael@0 | 4184 | bool |
michael@0 | 4185 | nsEditor::IsIMEComposing() const |
michael@0 | 4186 | { |
michael@0 | 4187 | return mComposition && mComposition->IsComposing(); |
michael@0 | 4188 | } |
michael@0 | 4189 | |
michael@0 | 4190 | nsresult |
michael@0 | 4191 | nsEditor::DeleteSelectionAndPrepareToCreateNode() |
michael@0 | 4192 | { |
michael@0 | 4193 | nsresult res; |
michael@0 | 4194 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 4195 | NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
michael@0 | 4196 | MOZ_ASSERT(selection->GetAnchorFocusRange()); |
michael@0 | 4197 | |
michael@0 | 4198 | if (!selection->GetAnchorFocusRange()->Collapsed()) { |
michael@0 | 4199 | res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); |
michael@0 | 4200 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4201 | |
michael@0 | 4202 | MOZ_ASSERT(selection->GetAnchorFocusRange() && |
michael@0 | 4203 | selection->GetAnchorFocusRange()->Collapsed(), |
michael@0 | 4204 | "Selection not collapsed after delete"); |
michael@0 | 4205 | } |
michael@0 | 4206 | |
michael@0 | 4207 | // If the selection is a chardata node, split it if necessary and compute |
michael@0 | 4208 | // where to put the new node |
michael@0 | 4209 | nsCOMPtr<nsINode> node = selection->GetAnchorNode(); |
michael@0 | 4210 | MOZ_ASSERT(node, "Selection has no ranges in it"); |
michael@0 | 4211 | |
michael@0 | 4212 | if (node && node->IsNodeOfType(nsINode::eDATA_NODE)) { |
michael@0 | 4213 | NS_ASSERTION(node->GetParentNode(), |
michael@0 | 4214 | "It's impossible to insert into chardata with no parent -- " |
michael@0 | 4215 | "fix the caller"); |
michael@0 | 4216 | NS_ENSURE_STATE(node->GetParentNode()); |
michael@0 | 4217 | |
michael@0 | 4218 | uint32_t offset = selection->AnchorOffset(); |
michael@0 | 4219 | |
michael@0 | 4220 | if (offset == 0) { |
michael@0 | 4221 | res = selection->Collapse(node->GetParentNode(), |
michael@0 | 4222 | node->GetParentNode()->IndexOf(node)); |
michael@0 | 4223 | MOZ_ASSERT(NS_SUCCEEDED(res)); |
michael@0 | 4224 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4225 | } else if (offset == node->Length()) { |
michael@0 | 4226 | res = selection->Collapse(node->GetParentNode(), |
michael@0 | 4227 | node->GetParentNode()->IndexOf(node) + 1); |
michael@0 | 4228 | MOZ_ASSERT(NS_SUCCEEDED(res)); |
michael@0 | 4229 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4230 | } else { |
michael@0 | 4231 | nsCOMPtr<nsIDOMNode> tmp; |
michael@0 | 4232 | res = SplitNode(node->AsDOMNode(), offset, getter_AddRefs(tmp)); |
michael@0 | 4233 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4234 | res = selection->Collapse(node->GetParentNode(), |
michael@0 | 4235 | node->GetParentNode()->IndexOf(node)); |
michael@0 | 4236 | MOZ_ASSERT(NS_SUCCEEDED(res)); |
michael@0 | 4237 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4238 | } |
michael@0 | 4239 | } |
michael@0 | 4240 | return NS_OK; |
michael@0 | 4241 | } |
michael@0 | 4242 | |
michael@0 | 4243 | |
michael@0 | 4244 | |
michael@0 | 4245 | void |
michael@0 | 4246 | nsEditor::DoAfterDoTransaction(nsITransaction *aTxn) |
michael@0 | 4247 | { |
michael@0 | 4248 | bool isTransientTransaction; |
michael@0 | 4249 | MOZ_ALWAYS_TRUE(NS_SUCCEEDED( |
michael@0 | 4250 | aTxn->GetIsTransient(&isTransientTransaction))); |
michael@0 | 4251 | |
michael@0 | 4252 | if (!isTransientTransaction) |
michael@0 | 4253 | { |
michael@0 | 4254 | // we need to deal here with the case where the user saved after some |
michael@0 | 4255 | // edits, then undid one or more times. Then, the undo count is -ve, |
michael@0 | 4256 | // but we can't let a do take it back to zero. So we flip it up to |
michael@0 | 4257 | // a +ve number. |
michael@0 | 4258 | int32_t modCount; |
michael@0 | 4259 | GetModificationCount(&modCount); |
michael@0 | 4260 | if (modCount < 0) |
michael@0 | 4261 | modCount = -modCount; |
michael@0 | 4262 | |
michael@0 | 4263 | // don't count transient transactions |
michael@0 | 4264 | MOZ_ALWAYS_TRUE(NS_SUCCEEDED( |
michael@0 | 4265 | IncrementModificationCount(1))); |
michael@0 | 4266 | } |
michael@0 | 4267 | } |
michael@0 | 4268 | |
michael@0 | 4269 | |
michael@0 | 4270 | void |
michael@0 | 4271 | nsEditor::DoAfterUndoTransaction() |
michael@0 | 4272 | { |
michael@0 | 4273 | // all undoable transactions are non-transient |
michael@0 | 4274 | MOZ_ALWAYS_TRUE(NS_SUCCEEDED( |
michael@0 | 4275 | IncrementModificationCount(-1))); |
michael@0 | 4276 | } |
michael@0 | 4277 | |
michael@0 | 4278 | void |
michael@0 | 4279 | nsEditor::DoAfterRedoTransaction() |
michael@0 | 4280 | { |
michael@0 | 4281 | // all redoable transactions are non-transient |
michael@0 | 4282 | MOZ_ALWAYS_TRUE(NS_SUCCEEDED( |
michael@0 | 4283 | IncrementModificationCount(1))); |
michael@0 | 4284 | } |
michael@0 | 4285 | |
michael@0 | 4286 | NS_IMETHODIMP |
michael@0 | 4287 | nsEditor::CreateTxnForSetAttribute(nsIDOMElement *aElement, |
michael@0 | 4288 | const nsAString& aAttribute, |
michael@0 | 4289 | const nsAString& aValue, |
michael@0 | 4290 | ChangeAttributeTxn ** aTxn) |
michael@0 | 4291 | { |
michael@0 | 4292 | NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
michael@0 | 4293 | |
michael@0 | 4294 | nsRefPtr<ChangeAttributeTxn> txn = new ChangeAttributeTxn(); |
michael@0 | 4295 | |
michael@0 | 4296 | nsresult rv = txn->Init(this, aElement, aAttribute, aValue, false); |
michael@0 | 4297 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4298 | { |
michael@0 | 4299 | txn.forget(aTxn); |
michael@0 | 4300 | } |
michael@0 | 4301 | |
michael@0 | 4302 | return rv; |
michael@0 | 4303 | } |
michael@0 | 4304 | |
michael@0 | 4305 | |
michael@0 | 4306 | NS_IMETHODIMP |
michael@0 | 4307 | nsEditor::CreateTxnForRemoveAttribute(nsIDOMElement *aElement, |
michael@0 | 4308 | const nsAString& aAttribute, |
michael@0 | 4309 | ChangeAttributeTxn ** aTxn) |
michael@0 | 4310 | { |
michael@0 | 4311 | NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
michael@0 | 4312 | |
michael@0 | 4313 | nsRefPtr<ChangeAttributeTxn> txn = new ChangeAttributeTxn(); |
michael@0 | 4314 | |
michael@0 | 4315 | nsresult rv = txn->Init(this, aElement, aAttribute, EmptyString(), true); |
michael@0 | 4316 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4317 | { |
michael@0 | 4318 | txn.forget(aTxn); |
michael@0 | 4319 | } |
michael@0 | 4320 | |
michael@0 | 4321 | return rv; |
michael@0 | 4322 | } |
michael@0 | 4323 | |
michael@0 | 4324 | |
michael@0 | 4325 | NS_IMETHODIMP nsEditor::CreateTxnForCreateElement(const nsAString& aTag, |
michael@0 | 4326 | nsIDOMNode *aParent, |
michael@0 | 4327 | int32_t aPosition, |
michael@0 | 4328 | CreateElementTxn ** aTxn) |
michael@0 | 4329 | { |
michael@0 | 4330 | NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER); |
michael@0 | 4331 | |
michael@0 | 4332 | nsRefPtr<CreateElementTxn> txn = new CreateElementTxn(); |
michael@0 | 4333 | |
michael@0 | 4334 | nsresult rv = txn->Init(this, aTag, aParent, aPosition); |
michael@0 | 4335 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4336 | { |
michael@0 | 4337 | txn.forget(aTxn); |
michael@0 | 4338 | } |
michael@0 | 4339 | |
michael@0 | 4340 | return rv; |
michael@0 | 4341 | } |
michael@0 | 4342 | |
michael@0 | 4343 | |
michael@0 | 4344 | NS_IMETHODIMP nsEditor::CreateTxnForInsertElement(nsIDOMNode * aNode, |
michael@0 | 4345 | nsIDOMNode * aParent, |
michael@0 | 4346 | int32_t aPosition, |
michael@0 | 4347 | InsertElementTxn ** aTxn) |
michael@0 | 4348 | { |
michael@0 | 4349 | NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER); |
michael@0 | 4350 | |
michael@0 | 4351 | nsRefPtr<InsertElementTxn> txn = new InsertElementTxn(); |
michael@0 | 4352 | |
michael@0 | 4353 | nsresult rv = txn->Init(aNode, aParent, aPosition, this); |
michael@0 | 4354 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4355 | { |
michael@0 | 4356 | txn.forget(aTxn); |
michael@0 | 4357 | } |
michael@0 | 4358 | |
michael@0 | 4359 | return rv; |
michael@0 | 4360 | } |
michael@0 | 4361 | |
michael@0 | 4362 | nsresult |
michael@0 | 4363 | nsEditor::CreateTxnForDeleteNode(nsINode* aNode, DeleteNodeTxn** aTxn) |
michael@0 | 4364 | { |
michael@0 | 4365 | NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
michael@0 | 4366 | |
michael@0 | 4367 | nsRefPtr<DeleteNodeTxn> txn = new DeleteNodeTxn(); |
michael@0 | 4368 | |
michael@0 | 4369 | nsresult res = txn->Init(this, aNode, &mRangeUpdater); |
michael@0 | 4370 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4371 | |
michael@0 | 4372 | txn.forget(aTxn); |
michael@0 | 4373 | return NS_OK; |
michael@0 | 4374 | } |
michael@0 | 4375 | |
michael@0 | 4376 | NS_IMETHODIMP |
michael@0 | 4377 | nsEditor::CreateTxnForIMEText(const nsAString& aStringToInsert, |
michael@0 | 4378 | IMETextTxn ** aTxn) |
michael@0 | 4379 | { |
michael@0 | 4380 | NS_ASSERTION(aTxn, "illegal value- null ptr- aTxn"); |
michael@0 | 4381 | |
michael@0 | 4382 | nsRefPtr<IMETextTxn> txn = new IMETextTxn(); |
michael@0 | 4383 | |
michael@0 | 4384 | // During handling IME composition, mComposition must have been initialized. |
michael@0 | 4385 | // TODO: We can simplify IMETextTxn::Init() with TextComposition class. |
michael@0 | 4386 | nsresult rv = txn->Init(mIMETextNode, mIMETextOffset, |
michael@0 | 4387 | mComposition->String().Length(), |
michael@0 | 4388 | mComposition->GetRanges(), aStringToInsert, this); |
michael@0 | 4389 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4390 | { |
michael@0 | 4391 | txn.forget(aTxn); |
michael@0 | 4392 | } |
michael@0 | 4393 | |
michael@0 | 4394 | return rv; |
michael@0 | 4395 | } |
michael@0 | 4396 | |
michael@0 | 4397 | |
michael@0 | 4398 | NS_IMETHODIMP |
michael@0 | 4399 | nsEditor::CreateTxnForAddStyleSheet(nsCSSStyleSheet* aSheet, AddStyleSheetTxn* *aTxn) |
michael@0 | 4400 | { |
michael@0 | 4401 | nsRefPtr<AddStyleSheetTxn> txn = new AddStyleSheetTxn(); |
michael@0 | 4402 | |
michael@0 | 4403 | nsresult rv = txn->Init(this, aSheet); |
michael@0 | 4404 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4405 | { |
michael@0 | 4406 | txn.forget(aTxn); |
michael@0 | 4407 | } |
michael@0 | 4408 | |
michael@0 | 4409 | return rv; |
michael@0 | 4410 | } |
michael@0 | 4411 | |
michael@0 | 4412 | |
michael@0 | 4413 | |
michael@0 | 4414 | NS_IMETHODIMP |
michael@0 | 4415 | nsEditor::CreateTxnForRemoveStyleSheet(nsCSSStyleSheet* aSheet, RemoveStyleSheetTxn* *aTxn) |
michael@0 | 4416 | { |
michael@0 | 4417 | nsRefPtr<RemoveStyleSheetTxn> txn = new RemoveStyleSheetTxn(); |
michael@0 | 4418 | |
michael@0 | 4419 | nsresult rv = txn->Init(this, aSheet); |
michael@0 | 4420 | if (NS_SUCCEEDED(rv)) |
michael@0 | 4421 | { |
michael@0 | 4422 | txn.forget(aTxn); |
michael@0 | 4423 | } |
michael@0 | 4424 | |
michael@0 | 4425 | return rv; |
michael@0 | 4426 | } |
michael@0 | 4427 | |
michael@0 | 4428 | |
michael@0 | 4429 | nsresult |
michael@0 | 4430 | nsEditor::CreateTxnForDeleteSelection(EDirection aAction, |
michael@0 | 4431 | EditAggregateTxn** aTxn, |
michael@0 | 4432 | nsINode** aNode, |
michael@0 | 4433 | int32_t* aOffset, |
michael@0 | 4434 | int32_t* aLength) |
michael@0 | 4435 | { |
michael@0 | 4436 | MOZ_ASSERT(aTxn); |
michael@0 | 4437 | *aTxn = nullptr; |
michael@0 | 4438 | |
michael@0 | 4439 | nsRefPtr<Selection> selection = GetSelection(); |
michael@0 | 4440 | NS_ENSURE_STATE(selection); |
michael@0 | 4441 | |
michael@0 | 4442 | // Check whether the selection is collapsed and we should do nothing: |
michael@0 | 4443 | if (selection->Collapsed() && aAction == eNone) { |
michael@0 | 4444 | return NS_OK; |
michael@0 | 4445 | } |
michael@0 | 4446 | |
michael@0 | 4447 | // allocate the out-param transaction |
michael@0 | 4448 | nsRefPtr<EditAggregateTxn> aggTxn = new EditAggregateTxn(); |
michael@0 | 4449 | |
michael@0 | 4450 | for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) { |
michael@0 | 4451 | nsRefPtr<nsRange> range = selection->GetRangeAt(rangeIdx); |
michael@0 | 4452 | NS_ENSURE_STATE(range); |
michael@0 | 4453 | |
michael@0 | 4454 | // Same with range as with selection; if it is collapsed and action |
michael@0 | 4455 | // is eNone, do nothing. |
michael@0 | 4456 | if (!range->Collapsed()) { |
michael@0 | 4457 | nsRefPtr<DeleteRangeTxn> txn = new DeleteRangeTxn(); |
michael@0 | 4458 | txn->Init(this, range, &mRangeUpdater); |
michael@0 | 4459 | aggTxn->AppendChild(txn); |
michael@0 | 4460 | } else if (aAction != eNone) { |
michael@0 | 4461 | // we have an insertion point. delete the thing in front of it or |
michael@0 | 4462 | // behind it, depending on aAction |
michael@0 | 4463 | nsresult res = CreateTxnForDeleteInsertionPoint(range, aAction, aggTxn, |
michael@0 | 4464 | aNode, aOffset, aLength); |
michael@0 | 4465 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4466 | } |
michael@0 | 4467 | } |
michael@0 | 4468 | |
michael@0 | 4469 | aggTxn.forget(aTxn); |
michael@0 | 4470 | |
michael@0 | 4471 | return NS_OK; |
michael@0 | 4472 | } |
michael@0 | 4473 | |
michael@0 | 4474 | nsresult |
michael@0 | 4475 | nsEditor::CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData, |
michael@0 | 4476 | uint32_t aOffset, |
michael@0 | 4477 | EDirection aDirection, |
michael@0 | 4478 | DeleteTextTxn** aTxn) |
michael@0 | 4479 | { |
michael@0 | 4480 | NS_ASSERTION(aDirection == eNext || aDirection == ePrevious, |
michael@0 | 4481 | "invalid direction"); |
michael@0 | 4482 | nsAutoString data; |
michael@0 | 4483 | aData->GetData(data); |
michael@0 | 4484 | NS_ASSERTION(data.Length(), "Trying to delete from a zero-length node"); |
michael@0 | 4485 | NS_ENSURE_STATE(data.Length()); |
michael@0 | 4486 | |
michael@0 | 4487 | uint32_t segOffset = aOffset, segLength = 1; |
michael@0 | 4488 | if (aDirection == eNext) { |
michael@0 | 4489 | if (segOffset + 1 < data.Length() && |
michael@0 | 4490 | NS_IS_HIGH_SURROGATE(data[segOffset]) && |
michael@0 | 4491 | NS_IS_LOW_SURROGATE(data[segOffset+1])) { |
michael@0 | 4492 | // delete both halves of the surrogate pair |
michael@0 | 4493 | ++segLength; |
michael@0 | 4494 | } |
michael@0 | 4495 | } else if (aOffset > 0) { |
michael@0 | 4496 | --segOffset; |
michael@0 | 4497 | if (segOffset > 0 && |
michael@0 | 4498 | NS_IS_LOW_SURROGATE(data[segOffset]) && |
michael@0 | 4499 | NS_IS_HIGH_SURROGATE(data[segOffset-1])) { |
michael@0 | 4500 | ++segLength; |
michael@0 | 4501 | --segOffset; |
michael@0 | 4502 | } |
michael@0 | 4503 | } else { |
michael@0 | 4504 | return NS_ERROR_FAILURE; |
michael@0 | 4505 | } |
michael@0 | 4506 | return CreateTxnForDeleteText(aData, segOffset, segLength, aTxn); |
michael@0 | 4507 | } |
michael@0 | 4508 | |
michael@0 | 4509 | //XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior |
michael@0 | 4510 | //are not implemented |
michael@0 | 4511 | nsresult |
michael@0 | 4512 | nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange, |
michael@0 | 4513 | EDirection aAction, |
michael@0 | 4514 | EditAggregateTxn* aTxn, |
michael@0 | 4515 | nsINode** aNode, |
michael@0 | 4516 | int32_t* aOffset, |
michael@0 | 4517 | int32_t* aLength) |
michael@0 | 4518 | { |
michael@0 | 4519 | MOZ_ASSERT(aAction != eNone); |
michael@0 | 4520 | |
michael@0 | 4521 | nsresult res; |
michael@0 | 4522 | |
michael@0 | 4523 | // get the node and offset of the insertion point |
michael@0 | 4524 | nsCOMPtr<nsINode> node = aRange->GetStartParent(); |
michael@0 | 4525 | NS_ENSURE_STATE(node); |
michael@0 | 4526 | |
michael@0 | 4527 | int32_t offset = aRange->StartOffset(); |
michael@0 | 4528 | |
michael@0 | 4529 | // determine if the insertion point is at the beginning, middle, or end of |
michael@0 | 4530 | // the node |
michael@0 | 4531 | nsCOMPtr<nsIDOMCharacterData> nodeAsCharData = do_QueryInterface(node); |
michael@0 | 4532 | |
michael@0 | 4533 | uint32_t count = node->Length(); |
michael@0 | 4534 | |
michael@0 | 4535 | bool isFirst = (0 == offset); |
michael@0 | 4536 | bool isLast = (count == (uint32_t)offset); |
michael@0 | 4537 | |
michael@0 | 4538 | // XXX: if isFirst && isLast, then we'll need to delete the node |
michael@0 | 4539 | // as well as the 1 child |
michael@0 | 4540 | |
michael@0 | 4541 | // build a transaction for deleting the appropriate data |
michael@0 | 4542 | // XXX: this has to come from rule section |
michael@0 | 4543 | if (aAction == ePrevious && isFirst) { |
michael@0 | 4544 | // we're backspacing from the beginning of the node. Delete the first |
michael@0 | 4545 | // thing to our left |
michael@0 | 4546 | nsCOMPtr<nsIContent> priorNode = GetPriorNode(node, true); |
michael@0 | 4547 | NS_ENSURE_STATE(priorNode); |
michael@0 | 4548 | |
michael@0 | 4549 | // there is a priorNode, so delete its last child (if chardata, delete the |
michael@0 | 4550 | // last char). if it has no children, delete it |
michael@0 | 4551 | nsCOMPtr<nsIDOMCharacterData> priorNodeAsCharData = |
michael@0 | 4552 | do_QueryInterface(priorNode); |
michael@0 | 4553 | if (priorNodeAsCharData) { |
michael@0 | 4554 | uint32_t length = priorNode->Length(); |
michael@0 | 4555 | // Bail out for empty chardata XXX: Do we want to do something else? |
michael@0 | 4556 | NS_ENSURE_STATE(length); |
michael@0 | 4557 | nsRefPtr<DeleteTextTxn> txn; |
michael@0 | 4558 | res = CreateTxnForDeleteCharacter(priorNodeAsCharData, length, |
michael@0 | 4559 | ePrevious, getter_AddRefs(txn)); |
michael@0 | 4560 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4561 | |
michael@0 | 4562 | *aOffset = txn->GetOffset(); |
michael@0 | 4563 | *aLength = txn->GetNumCharsToDelete(); |
michael@0 | 4564 | aTxn->AppendChild(txn); |
michael@0 | 4565 | } else { |
michael@0 | 4566 | // priorNode is not chardata, so tell its parent to delete it |
michael@0 | 4567 | nsRefPtr<DeleteNodeTxn> txn; |
michael@0 | 4568 | res = CreateTxnForDeleteNode(priorNode, getter_AddRefs(txn)); |
michael@0 | 4569 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4570 | |
michael@0 | 4571 | aTxn->AppendChild(txn); |
michael@0 | 4572 | } |
michael@0 | 4573 | |
michael@0 | 4574 | NS_ADDREF(*aNode = priorNode); |
michael@0 | 4575 | |
michael@0 | 4576 | return NS_OK; |
michael@0 | 4577 | } |
michael@0 | 4578 | |
michael@0 | 4579 | if (aAction == eNext && isLast) { |
michael@0 | 4580 | // we're deleting from the end of the node. Delete the first thing to our |
michael@0 | 4581 | // right |
michael@0 | 4582 | nsCOMPtr<nsIContent> nextNode = GetNextNode(node, true); |
michael@0 | 4583 | NS_ENSURE_STATE(nextNode); |
michael@0 | 4584 | |
michael@0 | 4585 | // there is a nextNode, so delete its first child (if chardata, delete the |
michael@0 | 4586 | // first char). if it has no children, delete it |
michael@0 | 4587 | nsCOMPtr<nsIDOMCharacterData> nextNodeAsCharData = |
michael@0 | 4588 | do_QueryInterface(nextNode); |
michael@0 | 4589 | if (nextNodeAsCharData) { |
michael@0 | 4590 | uint32_t length = nextNode->Length(); |
michael@0 | 4591 | // Bail out for empty chardata XXX: Do we want to do something else? |
michael@0 | 4592 | NS_ENSURE_STATE(length); |
michael@0 | 4593 | nsRefPtr<DeleteTextTxn> txn; |
michael@0 | 4594 | res = CreateTxnForDeleteCharacter(nextNodeAsCharData, 0, eNext, |
michael@0 | 4595 | getter_AddRefs(txn)); |
michael@0 | 4596 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4597 | |
michael@0 | 4598 | *aOffset = txn->GetOffset(); |
michael@0 | 4599 | *aLength = txn->GetNumCharsToDelete(); |
michael@0 | 4600 | aTxn->AppendChild(txn); |
michael@0 | 4601 | } else { |
michael@0 | 4602 | // nextNode is not chardata, so tell its parent to delete it |
michael@0 | 4603 | nsRefPtr<DeleteNodeTxn> txn; |
michael@0 | 4604 | res = CreateTxnForDeleteNode(nextNode, getter_AddRefs(txn)); |
michael@0 | 4605 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4606 | aTxn->AppendChild(txn); |
michael@0 | 4607 | } |
michael@0 | 4608 | |
michael@0 | 4609 | NS_ADDREF(*aNode = nextNode); |
michael@0 | 4610 | |
michael@0 | 4611 | return NS_OK; |
michael@0 | 4612 | } |
michael@0 | 4613 | |
michael@0 | 4614 | if (nodeAsCharData) { |
michael@0 | 4615 | // we have chardata, so delete a char at the proper offset |
michael@0 | 4616 | nsRefPtr<DeleteTextTxn> txn; |
michael@0 | 4617 | res = CreateTxnForDeleteCharacter(nodeAsCharData, offset, aAction, |
michael@0 | 4618 | getter_AddRefs(txn)); |
michael@0 | 4619 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4620 | |
michael@0 | 4621 | aTxn->AppendChild(txn); |
michael@0 | 4622 | NS_ADDREF(*aNode = node); |
michael@0 | 4623 | *aOffset = txn->GetOffset(); |
michael@0 | 4624 | *aLength = txn->GetNumCharsToDelete(); |
michael@0 | 4625 | } else { |
michael@0 | 4626 | // we're either deleting a node or chardata, need to dig into the next/prev |
michael@0 | 4627 | // node to find out |
michael@0 | 4628 | nsCOMPtr<nsINode> selectedNode; |
michael@0 | 4629 | if (aAction == ePrevious) { |
michael@0 | 4630 | selectedNode = GetPriorNode(node, offset, true); |
michael@0 | 4631 | } else if (aAction == eNext) { |
michael@0 | 4632 | selectedNode = GetNextNode(node, offset, true); |
michael@0 | 4633 | } |
michael@0 | 4634 | |
michael@0 | 4635 | while (selectedNode && |
michael@0 | 4636 | selectedNode->IsNodeOfType(nsINode::eDATA_NODE) && |
michael@0 | 4637 | !selectedNode->Length()) { |
michael@0 | 4638 | // Can't delete an empty chardata node (bug 762183) |
michael@0 | 4639 | if (aAction == ePrevious) { |
michael@0 | 4640 | selectedNode = GetPriorNode(selectedNode, true); |
michael@0 | 4641 | } else if (aAction == eNext) { |
michael@0 | 4642 | selectedNode = GetNextNode(selectedNode, true); |
michael@0 | 4643 | } |
michael@0 | 4644 | } |
michael@0 | 4645 | NS_ENSURE_STATE(selectedNode); |
michael@0 | 4646 | |
michael@0 | 4647 | nsCOMPtr<nsIDOMCharacterData> selectedNodeAsCharData = |
michael@0 | 4648 | do_QueryInterface(selectedNode); |
michael@0 | 4649 | if (selectedNodeAsCharData) { |
michael@0 | 4650 | // we are deleting from a chardata node, so do a character deletion |
michael@0 | 4651 | uint32_t position = 0; |
michael@0 | 4652 | if (aAction == ePrevious) { |
michael@0 | 4653 | position = selectedNode->Length(); |
michael@0 | 4654 | } |
michael@0 | 4655 | nsRefPtr<DeleteTextTxn> delTextTxn; |
michael@0 | 4656 | res = CreateTxnForDeleteCharacter(selectedNodeAsCharData, position, |
michael@0 | 4657 | aAction, getter_AddRefs(delTextTxn)); |
michael@0 | 4658 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4659 | NS_ENSURE_TRUE(delTextTxn, NS_ERROR_NULL_POINTER); |
michael@0 | 4660 | |
michael@0 | 4661 | aTxn->AppendChild(delTextTxn); |
michael@0 | 4662 | *aOffset = delTextTxn->GetOffset(); |
michael@0 | 4663 | *aLength = delTextTxn->GetNumCharsToDelete(); |
michael@0 | 4664 | } else { |
michael@0 | 4665 | nsRefPtr<DeleteNodeTxn> delElementTxn; |
michael@0 | 4666 | res = CreateTxnForDeleteNode(selectedNode, getter_AddRefs(delElementTxn)); |
michael@0 | 4667 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4668 | NS_ENSURE_TRUE(delElementTxn, NS_ERROR_NULL_POINTER); |
michael@0 | 4669 | |
michael@0 | 4670 | aTxn->AppendChild(delElementTxn); |
michael@0 | 4671 | } |
michael@0 | 4672 | |
michael@0 | 4673 | NS_ADDREF(*aNode = selectedNode); |
michael@0 | 4674 | } |
michael@0 | 4675 | |
michael@0 | 4676 | return NS_OK; |
michael@0 | 4677 | } |
michael@0 | 4678 | |
michael@0 | 4679 | nsresult |
michael@0 | 4680 | nsEditor::CreateRange(nsIDOMNode *aStartParent, int32_t aStartOffset, |
michael@0 | 4681 | nsIDOMNode *aEndParent, int32_t aEndOffset, |
michael@0 | 4682 | nsIDOMRange **aRange) |
michael@0 | 4683 | { |
michael@0 | 4684 | return nsRange::CreateRange(aStartParent, aStartOffset, aEndParent, |
michael@0 | 4685 | aEndOffset, aRange); |
michael@0 | 4686 | } |
michael@0 | 4687 | |
michael@0 | 4688 | nsresult |
michael@0 | 4689 | nsEditor::AppendNodeToSelectionAsRange(nsIDOMNode *aNode) |
michael@0 | 4690 | { |
michael@0 | 4691 | NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
michael@0 | 4692 | nsCOMPtr<nsISelection> selection; |
michael@0 | 4693 | nsresult res = GetSelection(getter_AddRefs(selection)); |
michael@0 | 4694 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4695 | if(!selection) return NS_ERROR_FAILURE; |
michael@0 | 4696 | |
michael@0 | 4697 | nsCOMPtr<nsIDOMNode> parentNode; |
michael@0 | 4698 | res = aNode->GetParentNode(getter_AddRefs(parentNode)); |
michael@0 | 4699 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4700 | NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER); |
michael@0 | 4701 | |
michael@0 | 4702 | int32_t offset = GetChildOffset(aNode, parentNode); |
michael@0 | 4703 | |
michael@0 | 4704 | nsCOMPtr<nsIDOMRange> range; |
michael@0 | 4705 | res = CreateRange(parentNode, offset, parentNode, offset+1, getter_AddRefs(range)); |
michael@0 | 4706 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4707 | NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); |
michael@0 | 4708 | |
michael@0 | 4709 | return selection->AddRange(range); |
michael@0 | 4710 | } |
michael@0 | 4711 | |
michael@0 | 4712 | nsresult nsEditor::ClearSelection() |
michael@0 | 4713 | { |
michael@0 | 4714 | nsCOMPtr<nsISelection> selection; |
michael@0 | 4715 | nsresult res = nsEditor::GetSelection(getter_AddRefs(selection)); |
michael@0 | 4716 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 4717 | NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
michael@0 | 4718 | return selection->RemoveAllRanges(); |
michael@0 | 4719 | } |
michael@0 | 4720 | |
michael@0 | 4721 | nsresult |
michael@0 | 4722 | nsEditor::CreateHTMLContent(const nsAString& aTag, dom::Element** aContent) |
michael@0 | 4723 | { |
michael@0 | 4724 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
michael@0 | 4725 | NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
michael@0 | 4726 | |
michael@0 | 4727 | // XXX Wallpaper over editor bug (editor tries to create elements with an |
michael@0 | 4728 | // empty nodename). |
michael@0 | 4729 | if (aTag.IsEmpty()) { |
michael@0 | 4730 | NS_ERROR("Don't pass an empty tag to nsEditor::CreateHTMLContent, " |
michael@0 | 4731 | "check caller."); |
michael@0 | 4732 | return NS_ERROR_FAILURE; |
michael@0 | 4733 | } |
michael@0 | 4734 | |
michael@0 | 4735 | return doc->CreateElem(aTag, nullptr, kNameSpaceID_XHTML, |
michael@0 | 4736 | reinterpret_cast<nsIContent**>(aContent)); |
michael@0 | 4737 | } |
michael@0 | 4738 | |
michael@0 | 4739 | nsresult |
michael@0 | 4740 | nsEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, |
michael@0 | 4741 | const nsAString & aAttribute, |
michael@0 | 4742 | const nsAString & aValue, |
michael@0 | 4743 | bool aSuppressTransaction) |
michael@0 | 4744 | { |
michael@0 | 4745 | return SetAttribute(aElement, aAttribute, aValue); |
michael@0 | 4746 | } |
michael@0 | 4747 | |
michael@0 | 4748 | nsresult |
michael@0 | 4749 | nsEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement, |
michael@0 | 4750 | const nsAString & aAttribute, |
michael@0 | 4751 | bool aSuppressTransaction) |
michael@0 | 4752 | { |
michael@0 | 4753 | return RemoveAttribute(aElement, aAttribute); |
michael@0 | 4754 | } |
michael@0 | 4755 | |
michael@0 | 4756 | nsresult |
michael@0 | 4757 | nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) |
michael@0 | 4758 | { |
michael@0 | 4759 | // NOTE: When you change this method, you should also change: |
michael@0 | 4760 | // * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html |
michael@0 | 4761 | // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html |
michael@0 | 4762 | // |
michael@0 | 4763 | // And also when you add new key handling, you need to change the subclass's |
michael@0 | 4764 | // HandleKeyPressEvent()'s switch statement. |
michael@0 | 4765 | |
michael@0 | 4766 | WidgetKeyboardEvent* nativeKeyEvent = |
michael@0 | 4767 | aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); |
michael@0 | 4768 | NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); |
michael@0 | 4769 | NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, |
michael@0 | 4770 | "HandleKeyPressEvent gets non-keypress event"); |
michael@0 | 4771 | |
michael@0 | 4772 | // if we are readonly or disabled, then do nothing. |
michael@0 | 4773 | if (IsReadonly() || IsDisabled()) { |
michael@0 | 4774 | // consume backspace for disabled and readonly textfields, to prevent |
michael@0 | 4775 | // back in history, which could be confusing to users |
michael@0 | 4776 | if (nativeKeyEvent->keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) { |
michael@0 | 4777 | aKeyEvent->PreventDefault(); |
michael@0 | 4778 | } |
michael@0 | 4779 | return NS_OK; |
michael@0 | 4780 | } |
michael@0 | 4781 | |
michael@0 | 4782 | switch (nativeKeyEvent->keyCode) { |
michael@0 | 4783 | case nsIDOMKeyEvent::DOM_VK_META: |
michael@0 | 4784 | case nsIDOMKeyEvent::DOM_VK_WIN: |
michael@0 | 4785 | case nsIDOMKeyEvent::DOM_VK_SHIFT: |
michael@0 | 4786 | case nsIDOMKeyEvent::DOM_VK_CONTROL: |
michael@0 | 4787 | case nsIDOMKeyEvent::DOM_VK_ALT: |
michael@0 | 4788 | aKeyEvent->PreventDefault(); // consumed |
michael@0 | 4789 | return NS_OK; |
michael@0 | 4790 | case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: |
michael@0 | 4791 | if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || |
michael@0 | 4792 | nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { |
michael@0 | 4793 | return NS_OK; |
michael@0 | 4794 | } |
michael@0 | 4795 | DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip); |
michael@0 | 4796 | aKeyEvent->PreventDefault(); // consumed |
michael@0 | 4797 | return NS_OK; |
michael@0 | 4798 | case nsIDOMKeyEvent::DOM_VK_DELETE: |
michael@0 | 4799 | // on certain platforms (such as windows) the shift key |
michael@0 | 4800 | // modifies what delete does (cmd_cut in this case). |
michael@0 | 4801 | // bailing here to allow the keybindings to do the cut. |
michael@0 | 4802 | if (nativeKeyEvent->IsShift() || nativeKeyEvent->IsControl() || |
michael@0 | 4803 | nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || |
michael@0 | 4804 | nativeKeyEvent->IsOS()) { |
michael@0 | 4805 | return NS_OK; |
michael@0 | 4806 | } |
michael@0 | 4807 | DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip); |
michael@0 | 4808 | aKeyEvent->PreventDefault(); // consumed |
michael@0 | 4809 | return NS_OK; |
michael@0 | 4810 | } |
michael@0 | 4811 | return NS_OK; |
michael@0 | 4812 | } |
michael@0 | 4813 | |
michael@0 | 4814 | nsresult |
michael@0 | 4815 | nsEditor::HandleInlineSpellCheck(EditAction action, |
michael@0 | 4816 | nsISelection *aSelection, |
michael@0 | 4817 | nsIDOMNode *previousSelectedNode, |
michael@0 | 4818 | int32_t previousSelectedOffset, |
michael@0 | 4819 | nsIDOMNode *aStartNode, |
michael@0 | 4820 | int32_t aStartOffset, |
michael@0 | 4821 | nsIDOMNode *aEndNode, |
michael@0 | 4822 | int32_t aEndOffset) |
michael@0 | 4823 | { |
michael@0 | 4824 | // Have to cast action here because this method is from an IDL |
michael@0 | 4825 | return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange( |
michael@0 | 4826 | (int32_t)action, aSelection, |
michael@0 | 4827 | previousSelectedNode, previousSelectedOffset, |
michael@0 | 4828 | aStartNode, aStartOffset, aEndNode, |
michael@0 | 4829 | aEndOffset) |
michael@0 | 4830 | : NS_OK; |
michael@0 | 4831 | } |
michael@0 | 4832 | |
michael@0 | 4833 | already_AddRefed<nsIContent> |
michael@0 | 4834 | nsEditor::FindSelectionRoot(nsINode *aNode) |
michael@0 | 4835 | { |
michael@0 | 4836 | nsCOMPtr<nsIContent> rootContent = GetRoot(); |
michael@0 | 4837 | return rootContent.forget(); |
michael@0 | 4838 | } |
michael@0 | 4839 | |
michael@0 | 4840 | nsresult |
michael@0 | 4841 | nsEditor::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget) |
michael@0 | 4842 | { |
michael@0 | 4843 | nsCOMPtr<nsINode> targetNode = do_QueryInterface(aFocusEventTarget); |
michael@0 | 4844 | NS_ENSURE_TRUE(targetNode, NS_ERROR_INVALID_ARG); |
michael@0 | 4845 | nsCOMPtr<nsIContent> selectionRootContent = FindSelectionRoot(targetNode); |
michael@0 | 4846 | if (!selectionRootContent) { |
michael@0 | 4847 | return NS_OK; |
michael@0 | 4848 | } |
michael@0 | 4849 | |
michael@0 | 4850 | bool isTargetDoc = |
michael@0 | 4851 | targetNode->NodeType() == nsIDOMNode::DOCUMENT_NODE && |
michael@0 | 4852 | targetNode->HasFlag(NODE_IS_EDITABLE); |
michael@0 | 4853 | |
michael@0 | 4854 | nsCOMPtr<nsISelection> selection; |
michael@0 | 4855 | nsresult rv = GetSelection(getter_AddRefs(selection)); |
michael@0 | 4856 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4857 | |
michael@0 | 4858 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 4859 | NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 4860 | |
michael@0 | 4861 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 4862 | rv = GetSelectionController(getter_AddRefs(selCon)); |
michael@0 | 4863 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 4864 | |
michael@0 | 4865 | nsCOMPtr<nsISelectionPrivate> selectionPrivate = |
michael@0 | 4866 | do_QueryInterface(selection); |
michael@0 | 4867 | NS_ENSURE_TRUE(selectionPrivate, NS_ERROR_UNEXPECTED); |
michael@0 | 4868 | |
michael@0 | 4869 | // Init the caret |
michael@0 | 4870 | nsRefPtr<nsCaret> caret = presShell->GetCaret(); |
michael@0 | 4871 | NS_ENSURE_TRUE(caret, NS_ERROR_UNEXPECTED); |
michael@0 | 4872 | caret->SetIgnoreUserModify(false); |
michael@0 | 4873 | caret->SetCaretDOMSelection(selection); |
michael@0 | 4874 | selCon->SetCaretReadOnly(IsReadonly()); |
michael@0 | 4875 | selCon->SetCaretEnabled(true); |
michael@0 | 4876 | |
michael@0 | 4877 | // Init selection |
michael@0 | 4878 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); |
michael@0 | 4879 | selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL); |
michael@0 | 4880 | selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 4881 | // If the computed selection root isn't root content, we should set it |
michael@0 | 4882 | // as selection ancestor limit. However, if that is root element, it means |
michael@0 | 4883 | // there is not limitation of the selection, then, we must set nullptr. |
michael@0 | 4884 | // NOTE: If we set a root element to the ancestor limit, some selection |
michael@0 | 4885 | // methods don't work fine. |
michael@0 | 4886 | if (selectionRootContent->GetParent()) { |
michael@0 | 4887 | selectionPrivate->SetAncestorLimiter(selectionRootContent); |
michael@0 | 4888 | } else { |
michael@0 | 4889 | selectionPrivate->SetAncestorLimiter(nullptr); |
michael@0 | 4890 | } |
michael@0 | 4891 | |
michael@0 | 4892 | // XXX What case needs this? |
michael@0 | 4893 | if (isTargetDoc) { |
michael@0 | 4894 | int32_t rangeCount; |
michael@0 | 4895 | selection->GetRangeCount(&rangeCount); |
michael@0 | 4896 | if (rangeCount == 0) { |
michael@0 | 4897 | BeginningOfDocument(); |
michael@0 | 4898 | } |
michael@0 | 4899 | } |
michael@0 | 4900 | |
michael@0 | 4901 | return NS_OK; |
michael@0 | 4902 | } |
michael@0 | 4903 | |
michael@0 | 4904 | void |
michael@0 | 4905 | nsEditor::FinalizeSelection() |
michael@0 | 4906 | { |
michael@0 | 4907 | nsCOMPtr<nsISelectionController> selCon; |
michael@0 | 4908 | nsresult rv = GetSelectionController(getter_AddRefs(selCon)); |
michael@0 | 4909 | NS_ENSURE_SUCCESS_VOID(rv); |
michael@0 | 4910 | |
michael@0 | 4911 | nsCOMPtr<nsISelection> selection; |
michael@0 | 4912 | rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, |
michael@0 | 4913 | getter_AddRefs(selection)); |
michael@0 | 4914 | NS_ENSURE_SUCCESS_VOID(rv); |
michael@0 | 4915 | |
michael@0 | 4916 | nsCOMPtr<nsISelectionPrivate> selectionPrivate = do_QueryInterface(selection); |
michael@0 | 4917 | NS_ENSURE_TRUE_VOID(selectionPrivate); |
michael@0 | 4918 | |
michael@0 | 4919 | selectionPrivate->SetAncestorLimiter(nullptr); |
michael@0 | 4920 | |
michael@0 | 4921 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 4922 | NS_ENSURE_TRUE_VOID(presShell); |
michael@0 | 4923 | |
michael@0 | 4924 | selCon->SetCaretEnabled(false); |
michael@0 | 4925 | |
michael@0 | 4926 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
michael@0 | 4927 | NS_ENSURE_TRUE_VOID(fm); |
michael@0 | 4928 | fm->UpdateCaretForCaretBrowsingMode(); |
michael@0 | 4929 | |
michael@0 | 4930 | if (!HasIndependentSelection()) { |
michael@0 | 4931 | // If this editor doesn't have an independent selection, i.e., it must |
michael@0 | 4932 | // mean that it is an HTML editor, the selection controller is shared with |
michael@0 | 4933 | // presShell. So, even this editor loses focus, other part of the document |
michael@0 | 4934 | // may still have focus. |
michael@0 | 4935 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
michael@0 | 4936 | ErrorResult ret; |
michael@0 | 4937 | if (!doc || !doc->HasFocus(ret)) { |
michael@0 | 4938 | // If the document already lost focus, mark the selection as disabled. |
michael@0 | 4939 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED); |
michael@0 | 4940 | } else { |
michael@0 | 4941 | // Otherwise, mark selection as normal because outside of a |
michael@0 | 4942 | // contenteditable element should be selected with normal selection |
michael@0 | 4943 | // color after here. |
michael@0 | 4944 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); |
michael@0 | 4945 | } |
michael@0 | 4946 | } else if (IsFormWidget() || IsPasswordEditor() || |
michael@0 | 4947 | IsReadonly() || IsDisabled() || IsInputFiltered()) { |
michael@0 | 4948 | // In <input> or <textarea>, the independent selection should be hidden |
michael@0 | 4949 | // while this editor doesn't have focus. |
michael@0 | 4950 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); |
michael@0 | 4951 | } else { |
michael@0 | 4952 | // Otherwise, although we're not sure how this case happens, the |
michael@0 | 4953 | // independent selection should be marked as disabled. |
michael@0 | 4954 | selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED); |
michael@0 | 4955 | } |
michael@0 | 4956 | |
michael@0 | 4957 | selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL); |
michael@0 | 4958 | } |
michael@0 | 4959 | |
michael@0 | 4960 | dom::Element * |
michael@0 | 4961 | nsEditor::GetRoot() |
michael@0 | 4962 | { |
michael@0 | 4963 | if (!mRootElement) |
michael@0 | 4964 | { |
michael@0 | 4965 | nsCOMPtr<nsIDOMElement> root; |
michael@0 | 4966 | |
michael@0 | 4967 | // Let GetRootElement() do the work |
michael@0 | 4968 | GetRootElement(getter_AddRefs(root)); |
michael@0 | 4969 | } |
michael@0 | 4970 | |
michael@0 | 4971 | return mRootElement; |
michael@0 | 4972 | } |
michael@0 | 4973 | |
michael@0 | 4974 | dom::Element* |
michael@0 | 4975 | nsEditor::GetEditorRoot() |
michael@0 | 4976 | { |
michael@0 | 4977 | return GetRoot(); |
michael@0 | 4978 | } |
michael@0 | 4979 | |
michael@0 | 4980 | Element* |
michael@0 | 4981 | nsEditor::GetExposedRoot() |
michael@0 | 4982 | { |
michael@0 | 4983 | Element* rootElement = GetRoot(); |
michael@0 | 4984 | |
michael@0 | 4985 | // For plaintext editors, we need to ask the input/textarea element directly. |
michael@0 | 4986 | if (rootElement && rootElement->IsRootOfNativeAnonymousSubtree()) { |
michael@0 | 4987 | rootElement = rootElement->GetParent()->AsElement(); |
michael@0 | 4988 | } |
michael@0 | 4989 | |
michael@0 | 4990 | return rootElement; |
michael@0 | 4991 | } |
michael@0 | 4992 | |
michael@0 | 4993 | nsresult |
michael@0 | 4994 | nsEditor::DetermineCurrentDirection() |
michael@0 | 4995 | { |
michael@0 | 4996 | // Get the current root direction from its frame |
michael@0 | 4997 | nsIContent* rootElement = GetExposedRoot(); |
michael@0 | 4998 | |
michael@0 | 4999 | // If we don't have an explicit direction, determine our direction |
michael@0 | 5000 | // from the content's direction |
michael@0 | 5001 | if (!(mFlags & (nsIPlaintextEditor::eEditorLeftToRight | |
michael@0 | 5002 | nsIPlaintextEditor::eEditorRightToLeft))) { |
michael@0 | 5003 | |
michael@0 | 5004 | nsIFrame* frame = rootElement->GetPrimaryFrame(); |
michael@0 | 5005 | NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); |
michael@0 | 5006 | |
michael@0 | 5007 | // Set the flag here, to enable us to use the same code path below. |
michael@0 | 5008 | // It will be flipped before returning from the function. |
michael@0 | 5009 | if (frame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { |
michael@0 | 5010 | mFlags |= nsIPlaintextEditor::eEditorRightToLeft; |
michael@0 | 5011 | } else { |
michael@0 | 5012 | mFlags |= nsIPlaintextEditor::eEditorLeftToRight; |
michael@0 | 5013 | } |
michael@0 | 5014 | } |
michael@0 | 5015 | |
michael@0 | 5016 | return NS_OK; |
michael@0 | 5017 | } |
michael@0 | 5018 | |
michael@0 | 5019 | NS_IMETHODIMP |
michael@0 | 5020 | nsEditor::SwitchTextDirection() |
michael@0 | 5021 | { |
michael@0 | 5022 | // Get the current root direction from its frame |
michael@0 | 5023 | nsIContent* rootElement = GetExposedRoot(); |
michael@0 | 5024 | |
michael@0 | 5025 | nsresult rv = DetermineCurrentDirection(); |
michael@0 | 5026 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 5027 | |
michael@0 | 5028 | // Apply the opposite direction |
michael@0 | 5029 | if (mFlags & nsIPlaintextEditor::eEditorRightToLeft) { |
michael@0 | 5030 | NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight), |
michael@0 | 5031 | "Unexpected mutually exclusive flag"); |
michael@0 | 5032 | mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft; |
michael@0 | 5033 | mFlags |= nsIPlaintextEditor::eEditorLeftToRight; |
michael@0 | 5034 | rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), true); |
michael@0 | 5035 | } else if (mFlags & nsIPlaintextEditor::eEditorLeftToRight) { |
michael@0 | 5036 | NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorRightToLeft), |
michael@0 | 5037 | "Unexpected mutually exclusive flag"); |
michael@0 | 5038 | mFlags |= nsIPlaintextEditor::eEditorRightToLeft; |
michael@0 | 5039 | mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight; |
michael@0 | 5040 | rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), true); |
michael@0 | 5041 | } |
michael@0 | 5042 | |
michael@0 | 5043 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 5044 | FireInputEvent(); |
michael@0 | 5045 | } |
michael@0 | 5046 | |
michael@0 | 5047 | return rv; |
michael@0 | 5048 | } |
michael@0 | 5049 | |
michael@0 | 5050 | void |
michael@0 | 5051 | nsEditor::SwitchTextDirectionTo(uint32_t aDirection) |
michael@0 | 5052 | { |
michael@0 | 5053 | // Get the current root direction from its frame |
michael@0 | 5054 | nsIContent* rootElement = GetExposedRoot(); |
michael@0 | 5055 | |
michael@0 | 5056 | nsresult rv = DetermineCurrentDirection(); |
michael@0 | 5057 | NS_ENSURE_SUCCESS_VOID(rv); |
michael@0 | 5058 | |
michael@0 | 5059 | // Apply the requested direction |
michael@0 | 5060 | if (aDirection == nsIPlaintextEditor::eEditorLeftToRight && |
michael@0 | 5061 | (mFlags & nsIPlaintextEditor::eEditorRightToLeft)) { |
michael@0 | 5062 | NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight), |
michael@0 | 5063 | "Unexpected mutually exclusive flag"); |
michael@0 | 5064 | mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft; |
michael@0 | 5065 | mFlags |= nsIPlaintextEditor::eEditorLeftToRight; |
michael@0 | 5066 | rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), true); |
michael@0 | 5067 | } else if (aDirection == nsIPlaintextEditor::eEditorRightToLeft && |
michael@0 | 5068 | (mFlags & nsIPlaintextEditor::eEditorLeftToRight)) { |
michael@0 | 5069 | NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorRightToLeft), |
michael@0 | 5070 | "Unexpected mutually exclusive flag"); |
michael@0 | 5071 | mFlags |= nsIPlaintextEditor::eEditorRightToLeft; |
michael@0 | 5072 | mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight; |
michael@0 | 5073 | rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), true); |
michael@0 | 5074 | } |
michael@0 | 5075 | |
michael@0 | 5076 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 5077 | FireInputEvent(); |
michael@0 | 5078 | } |
michael@0 | 5079 | } |
michael@0 | 5080 | |
michael@0 | 5081 | #if DEBUG_JOE |
michael@0 | 5082 | void |
michael@0 | 5083 | nsEditor::DumpNode(nsIDOMNode *aNode, int32_t indent) |
michael@0 | 5084 | { |
michael@0 | 5085 | int32_t i; |
michael@0 | 5086 | for (i=0; i<indent; i++) |
michael@0 | 5087 | printf(" "); |
michael@0 | 5088 | |
michael@0 | 5089 | nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); |
michael@0 | 5090 | nsCOMPtr<nsIDOMDocumentFragment> docfrag = do_QueryInterface(aNode); |
michael@0 | 5091 | |
michael@0 | 5092 | if (element || docfrag) |
michael@0 | 5093 | { |
michael@0 | 5094 | if (element) |
michael@0 | 5095 | { |
michael@0 | 5096 | nsAutoString tag; |
michael@0 | 5097 | element->GetTagName(tag); |
michael@0 | 5098 | printf("<%s>\n", NS_LossyConvertUTF16toASCII(tag).get()); |
michael@0 | 5099 | } |
michael@0 | 5100 | else |
michael@0 | 5101 | { |
michael@0 | 5102 | printf("<document fragment>\n"); |
michael@0 | 5103 | } |
michael@0 | 5104 | nsCOMPtr<nsIDOMNodeList> childList; |
michael@0 | 5105 | aNode->GetChildNodes(getter_AddRefs(childList)); |
michael@0 | 5106 | NS_ENSURE_TRUE(childList, NS_ERROR_NULL_POINTER); |
michael@0 | 5107 | uint32_t numChildren; |
michael@0 | 5108 | childList->GetLength(&numChildren); |
michael@0 | 5109 | nsCOMPtr<nsIDOMNode> child, tmp; |
michael@0 | 5110 | aNode->GetFirstChild(getter_AddRefs(child)); |
michael@0 | 5111 | for (i=0; i<numChildren; i++) |
michael@0 | 5112 | { |
michael@0 | 5113 | DumpNode(child, indent+1); |
michael@0 | 5114 | child->GetNextSibling(getter_AddRefs(tmp)); |
michael@0 | 5115 | child = tmp; |
michael@0 | 5116 | } |
michael@0 | 5117 | } |
michael@0 | 5118 | else if (IsTextNode(aNode)) |
michael@0 | 5119 | { |
michael@0 | 5120 | nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(aNode); |
michael@0 | 5121 | nsAutoString str; |
michael@0 | 5122 | textNode->GetData(str); |
michael@0 | 5123 | nsAutoCString cstr; |
michael@0 | 5124 | LossyCopyUTF16toASCII(str, cstr); |
michael@0 | 5125 | cstr.ReplaceChar('\n', ' '); |
michael@0 | 5126 | printf("<textnode> %s\n", cstr.get()); |
michael@0 | 5127 | } |
michael@0 | 5128 | } |
michael@0 | 5129 | #endif |
michael@0 | 5130 | |
michael@0 | 5131 | bool |
michael@0 | 5132 | nsEditor::IsModifiableNode(nsIDOMNode *aNode) |
michael@0 | 5133 | { |
michael@0 | 5134 | return true; |
michael@0 | 5135 | } |
michael@0 | 5136 | |
michael@0 | 5137 | bool |
michael@0 | 5138 | nsEditor::IsModifiableNode(nsINode *aNode) |
michael@0 | 5139 | { |
michael@0 | 5140 | return true; |
michael@0 | 5141 | } |
michael@0 | 5142 | |
michael@0 | 5143 | already_AddRefed<nsIContent> |
michael@0 | 5144 | nsEditor::GetFocusedContent() |
michael@0 | 5145 | { |
michael@0 | 5146 | nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget(); |
michael@0 | 5147 | if (!piTarget) { |
michael@0 | 5148 | return nullptr; |
michael@0 | 5149 | } |
michael@0 | 5150 | |
michael@0 | 5151 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
michael@0 | 5152 | NS_ENSURE_TRUE(fm, nullptr); |
michael@0 | 5153 | |
michael@0 | 5154 | nsCOMPtr<nsIContent> content = fm->GetFocusedContent(); |
michael@0 | 5155 | return SameCOMIdentity(content, piTarget) ? content.forget() : nullptr; |
michael@0 | 5156 | } |
michael@0 | 5157 | |
michael@0 | 5158 | already_AddRefed<nsIContent> |
michael@0 | 5159 | nsEditor::GetFocusedContentForIME() |
michael@0 | 5160 | { |
michael@0 | 5161 | return GetFocusedContent(); |
michael@0 | 5162 | } |
michael@0 | 5163 | |
michael@0 | 5164 | bool |
michael@0 | 5165 | nsEditor::IsActiveInDOMWindow() |
michael@0 | 5166 | { |
michael@0 | 5167 | nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget(); |
michael@0 | 5168 | if (!piTarget) { |
michael@0 | 5169 | return false; |
michael@0 | 5170 | } |
michael@0 | 5171 | |
michael@0 | 5172 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
michael@0 | 5173 | NS_ENSURE_TRUE(fm, false); |
michael@0 | 5174 | |
michael@0 | 5175 | nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
michael@0 | 5176 | nsPIDOMWindow* ourWindow = doc->GetWindow(); |
michael@0 | 5177 | nsCOMPtr<nsPIDOMWindow> win; |
michael@0 | 5178 | nsIContent* content = |
michael@0 | 5179 | nsFocusManager::GetFocusedDescendant(ourWindow, false, |
michael@0 | 5180 | getter_AddRefs(win)); |
michael@0 | 5181 | return SameCOMIdentity(content, piTarget); |
michael@0 | 5182 | } |
michael@0 | 5183 | |
michael@0 | 5184 | bool |
michael@0 | 5185 | nsEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) |
michael@0 | 5186 | { |
michael@0 | 5187 | // If the event is trusted, the event should always cause input. |
michael@0 | 5188 | NS_ENSURE_TRUE(aEvent, false); |
michael@0 | 5189 | |
michael@0 | 5190 | // If this is mouse event but this editor doesn't have focus, we shouldn't |
michael@0 | 5191 | // handle it. |
michael@0 | 5192 | nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); |
michael@0 | 5193 | if (mouseEvent) { |
michael@0 | 5194 | nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); |
michael@0 | 5195 | if (!focusedContent) { |
michael@0 | 5196 | return false; |
michael@0 | 5197 | } |
michael@0 | 5198 | } else { |
michael@0 | 5199 | nsAutoString eventType; |
michael@0 | 5200 | aEvent->GetType(eventType); |
michael@0 | 5201 | // If composition event or text event isn't dispatched via widget, |
michael@0 | 5202 | // we need to ignore them since they cannot be managed by TextComposition. |
michael@0 | 5203 | // E.g., the event was created by chrome JS. |
michael@0 | 5204 | // Note that if we allow to handle such events, editor may be confused by |
michael@0 | 5205 | // strange event order. |
michael@0 | 5206 | if (eventType.EqualsLiteral("text") || |
michael@0 | 5207 | eventType.EqualsLiteral("compositionstart") || |
michael@0 | 5208 | eventType.EqualsLiteral("compositionend")) { |
michael@0 | 5209 | WidgetGUIEvent* widgetGUIEvent = |
michael@0 | 5210 | aEvent->GetInternalNSEvent()->AsGUIEvent(); |
michael@0 | 5211 | if (!widgetGUIEvent || !widgetGUIEvent->widget) { |
michael@0 | 5212 | return false; |
michael@0 | 5213 | } |
michael@0 | 5214 | } |
michael@0 | 5215 | } |
michael@0 | 5216 | |
michael@0 | 5217 | bool isTrusted; |
michael@0 | 5218 | nsresult rv = aEvent->GetIsTrusted(&isTrusted); |
michael@0 | 5219 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 5220 | if (isTrusted) { |
michael@0 | 5221 | return true; |
michael@0 | 5222 | } |
michael@0 | 5223 | |
michael@0 | 5224 | // Ignore untrusted mouse event. |
michael@0 | 5225 | // XXX Why are we handling other untrusted input events? |
michael@0 | 5226 | if (mouseEvent) { |
michael@0 | 5227 | return false; |
michael@0 | 5228 | } |
michael@0 | 5229 | |
michael@0 | 5230 | // Otherwise, we shouldn't handle any input events when we're not an active |
michael@0 | 5231 | // element of the DOM window. |
michael@0 | 5232 | return IsActiveInDOMWindow(); |
michael@0 | 5233 | } |
michael@0 | 5234 | |
michael@0 | 5235 | void |
michael@0 | 5236 | nsEditor::OnFocus(nsIDOMEventTarget* aFocusEventTarget) |
michael@0 | 5237 | { |
michael@0 | 5238 | InitializeSelection(aFocusEventTarget); |
michael@0 | 5239 | if (mInlineSpellChecker) { |
michael@0 | 5240 | mInlineSpellChecker->UpdateCurrentDictionary(); |
michael@0 | 5241 | } |
michael@0 | 5242 | } |
michael@0 | 5243 | |
michael@0 | 5244 | NS_IMETHODIMP |
michael@0 | 5245 | nsEditor::GetSuppressDispatchingInputEvent(bool *aSuppressed) |
michael@0 | 5246 | { |
michael@0 | 5247 | NS_ENSURE_ARG_POINTER(aSuppressed); |
michael@0 | 5248 | *aSuppressed = !mDispatchInputEvent; |
michael@0 | 5249 | return NS_OK; |
michael@0 | 5250 | } |
michael@0 | 5251 | |
michael@0 | 5252 | NS_IMETHODIMP |
michael@0 | 5253 | nsEditor::SetSuppressDispatchingInputEvent(bool aSuppress) |
michael@0 | 5254 | { |
michael@0 | 5255 | mDispatchInputEvent = !aSuppress; |
michael@0 | 5256 | return NS_OK; |
michael@0 | 5257 | } |