editor/libeditor/base/nsEditor.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 }

mercurial