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