|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "ContentEventHandler.h" |
|
8 #include "IMEContentObserver.h" |
|
9 #include "mozilla/AsyncEventDispatcher.h" |
|
10 #include "mozilla/EventStateManager.h" |
|
11 #include "mozilla/IMEStateManager.h" |
|
12 #include "mozilla/TextComposition.h" |
|
13 #include "mozilla/dom/Element.h" |
|
14 #include "nsAutoPtr.h" |
|
15 #include "nsContentUtils.h" |
|
16 #include "nsGkAtoms.h" |
|
17 #include "nsIAtom.h" |
|
18 #include "nsIContent.h" |
|
19 #include "nsIDocument.h" |
|
20 #include "nsIDOMDocument.h" |
|
21 #include "nsIDOMRange.h" |
|
22 #include "nsIFrame.h" |
|
23 #include "nsINode.h" |
|
24 #include "nsIPresShell.h" |
|
25 #include "nsISelectionController.h" |
|
26 #include "nsISelectionPrivate.h" |
|
27 #include "nsISupports.h" |
|
28 #include "nsIWidget.h" |
|
29 #include "nsPresContext.h" |
|
30 #include "nsThreadUtils.h" |
|
31 #include "nsWeakReference.h" |
|
32 |
|
33 namespace mozilla { |
|
34 |
|
35 using namespace widget; |
|
36 |
|
37 NS_IMPL_CYCLE_COLLECTION(IMEContentObserver, |
|
38 mWidget, mSelection, |
|
39 mRootContent, mEditableNode, mDocShell) |
|
40 |
|
41 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver) |
|
42 NS_INTERFACE_MAP_ENTRY(nsISelectionListener) |
|
43 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) |
|
44 NS_INTERFACE_MAP_ENTRY(nsIReflowObserver) |
|
45 NS_INTERFACE_MAP_ENTRY(nsIScrollObserver) |
|
46 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
47 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelectionListener) |
|
48 NS_INTERFACE_MAP_END |
|
49 |
|
50 NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver) |
|
51 NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver) |
|
52 |
|
53 IMEContentObserver::IMEContentObserver() |
|
54 : mESM(nullptr) |
|
55 { |
|
56 } |
|
57 |
|
58 void |
|
59 IMEContentObserver::Init(nsIWidget* aWidget, |
|
60 nsPresContext* aPresContext, |
|
61 nsIContent* aContent) |
|
62 { |
|
63 mESM = aPresContext->EventStateManager(); |
|
64 mESM->OnStartToObserveContent(this); |
|
65 |
|
66 mWidget = aWidget; |
|
67 mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aContent); |
|
68 if (!mEditableNode) { |
|
69 return; |
|
70 } |
|
71 |
|
72 nsIPresShell* presShell = aPresContext->PresShell(); |
|
73 |
|
74 // get selection and root content |
|
75 nsCOMPtr<nsISelectionController> selCon; |
|
76 if (mEditableNode->IsNodeOfType(nsINode::eCONTENT)) { |
|
77 nsIFrame* frame = |
|
78 static_cast<nsIContent*>(mEditableNode.get())->GetPrimaryFrame(); |
|
79 NS_ENSURE_TRUE_VOID(frame); |
|
80 |
|
81 frame->GetSelectionController(aPresContext, |
|
82 getter_AddRefs(selCon)); |
|
83 } else { |
|
84 // mEditableNode is a document |
|
85 selCon = do_QueryInterface(presShell); |
|
86 } |
|
87 NS_ENSURE_TRUE_VOID(selCon); |
|
88 |
|
89 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, |
|
90 getter_AddRefs(mSelection)); |
|
91 NS_ENSURE_TRUE_VOID(mSelection); |
|
92 |
|
93 nsCOMPtr<nsIDOMRange> selDomRange; |
|
94 if (NS_SUCCEEDED(mSelection->GetRangeAt(0, getter_AddRefs(selDomRange)))) { |
|
95 nsRange* selRange = static_cast<nsRange*>(selDomRange.get()); |
|
96 NS_ENSURE_TRUE_VOID(selRange && selRange->GetStartParent()); |
|
97 |
|
98 mRootContent = selRange->GetStartParent()-> |
|
99 GetSelectionRootContent(presShell); |
|
100 } else { |
|
101 mRootContent = mEditableNode->GetSelectionRootContent(presShell); |
|
102 } |
|
103 if (!mRootContent && mEditableNode->IsNodeOfType(nsINode::eDOCUMENT)) { |
|
104 // The document node is editable, but there are no contents, this document |
|
105 // is not editable. |
|
106 return; |
|
107 } |
|
108 NS_ENSURE_TRUE_VOID(mRootContent); |
|
109 |
|
110 if (IMEStateManager::IsTestingIME()) { |
|
111 nsIDocument* doc = aPresContext->Document(); |
|
112 (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusIn"), |
|
113 false, false))->RunDOMEventWhenSafe(); |
|
114 } |
|
115 |
|
116 aWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS)); |
|
117 |
|
118 // NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver |
|
119 // instance via IMEStateManager::UpdateIMEState(). So, this |
|
120 // instance might already have been destroyed, check it. |
|
121 if (!mRootContent) { |
|
122 return; |
|
123 } |
|
124 |
|
125 mDocShell = aPresContext->GetDocShell(); |
|
126 |
|
127 ObserveEditableNode(); |
|
128 } |
|
129 |
|
130 void |
|
131 IMEContentObserver::ObserveEditableNode() |
|
132 { |
|
133 MOZ_ASSERT(mSelection); |
|
134 MOZ_ASSERT(mRootContent); |
|
135 |
|
136 mUpdatePreference = mWidget->GetIMEUpdatePreference(); |
|
137 if (mUpdatePreference.WantSelectionChange()) { |
|
138 // add selection change listener |
|
139 nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); |
|
140 NS_ENSURE_TRUE_VOID(selPrivate); |
|
141 nsresult rv = selPrivate->AddSelectionListener(this); |
|
142 NS_ENSURE_SUCCESS_VOID(rv); |
|
143 } |
|
144 |
|
145 if (mUpdatePreference.WantTextChange()) { |
|
146 // add text change observer |
|
147 mRootContent->AddMutationObserver(this); |
|
148 } |
|
149 |
|
150 if (mUpdatePreference.WantPositionChanged() && mDocShell) { |
|
151 // Add scroll position listener and reflow observer to detect position and |
|
152 // size changes |
|
153 mDocShell->AddWeakScrollObserver(this); |
|
154 mDocShell->AddWeakReflowObserver(this); |
|
155 } |
|
156 } |
|
157 |
|
158 void |
|
159 IMEContentObserver::Destroy() |
|
160 { |
|
161 // If CreateTextStateManager failed, mRootContent will be null, |
|
162 // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)) |
|
163 if (mRootContent) { |
|
164 if (IMEStateManager::IsTestingIME() && mEditableNode) { |
|
165 nsIDocument* doc = mEditableNode->OwnerDoc(); |
|
166 (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusOut"), |
|
167 false, false))->RunDOMEventWhenSafe(); |
|
168 } |
|
169 mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); |
|
170 } |
|
171 // Even if there are some pending notification, it'll never notify the widget. |
|
172 mWidget = nullptr; |
|
173 if (mUpdatePreference.WantSelectionChange() && mSelection) { |
|
174 nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); |
|
175 if (selPrivate) { |
|
176 selPrivate->RemoveSelectionListener(this); |
|
177 } |
|
178 } |
|
179 mSelection = nullptr; |
|
180 if (mUpdatePreference.WantTextChange() && mRootContent) { |
|
181 mRootContent->RemoveMutationObserver(this); |
|
182 } |
|
183 if (mUpdatePreference.WantPositionChanged() && mDocShell) { |
|
184 mDocShell->RemoveWeakScrollObserver(this); |
|
185 mDocShell->RemoveWeakReflowObserver(this); |
|
186 } |
|
187 mRootContent = nullptr; |
|
188 mEditableNode = nullptr; |
|
189 mDocShell = nullptr; |
|
190 mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; |
|
191 |
|
192 if (mESM) { |
|
193 mESM->OnStopObservingContent(this); |
|
194 mESM = nullptr; |
|
195 } |
|
196 } |
|
197 |
|
198 void |
|
199 IMEContentObserver::DisconnectFromEventStateManager() |
|
200 { |
|
201 mESM = nullptr; |
|
202 } |
|
203 |
|
204 bool |
|
205 IMEContentObserver::IsManaging(nsPresContext* aPresContext, |
|
206 nsIContent* aContent) |
|
207 { |
|
208 if (!mSelection || !mRootContent || !mEditableNode) { |
|
209 return false; // failed to initialize. |
|
210 } |
|
211 if (!mRootContent->IsInDoc()) { |
|
212 return false; // the focused editor has already been reframed. |
|
213 } |
|
214 return mEditableNode == IMEStateManager::GetRootEditableNode(aPresContext, |
|
215 aContent); |
|
216 } |
|
217 |
|
218 bool |
|
219 IMEContentObserver::IsEditorHandlingEventForComposition() const |
|
220 { |
|
221 if (!mWidget) { |
|
222 return false; |
|
223 } |
|
224 nsRefPtr<TextComposition> composition = |
|
225 IMEStateManager::GetTextCompositionFor(mWidget); |
|
226 if (!composition) { |
|
227 return false; |
|
228 } |
|
229 return composition->IsEditorHandlingEvent(); |
|
230 } |
|
231 |
|
232 nsresult |
|
233 IMEContentObserver::GetSelectionAndRoot(nsISelection** aSelection, |
|
234 nsIContent** aRootContent) const |
|
235 { |
|
236 if (!mEditableNode || !mSelection) { |
|
237 return NS_ERROR_NOT_AVAILABLE; |
|
238 } |
|
239 |
|
240 NS_ASSERTION(mSelection && mRootContent, "uninitialized content observer"); |
|
241 NS_ADDREF(*aSelection = mSelection); |
|
242 NS_ADDREF(*aRootContent = mRootContent); |
|
243 return NS_OK; |
|
244 } |
|
245 |
|
246 // Helper class, used for selection change notification |
|
247 class SelectionChangeEvent : public nsRunnable |
|
248 { |
|
249 public: |
|
250 SelectionChangeEvent(IMEContentObserver* aDispatcher, |
|
251 bool aCausedByComposition) |
|
252 : mDispatcher(aDispatcher) |
|
253 , mCausedByComposition(aCausedByComposition) |
|
254 { |
|
255 MOZ_ASSERT(mDispatcher); |
|
256 } |
|
257 |
|
258 NS_IMETHOD Run() |
|
259 { |
|
260 if (mDispatcher->GetWidget()) { |
|
261 IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE); |
|
262 notification.mSelectionChangeData.mCausedByComposition = |
|
263 mCausedByComposition; |
|
264 mDispatcher->GetWidget()->NotifyIME(notification); |
|
265 } |
|
266 return NS_OK; |
|
267 } |
|
268 |
|
269 private: |
|
270 nsRefPtr<IMEContentObserver> mDispatcher; |
|
271 bool mCausedByComposition; |
|
272 }; |
|
273 |
|
274 nsresult |
|
275 IMEContentObserver::NotifySelectionChanged(nsIDOMDocument* aDOMDocument, |
|
276 nsISelection* aSelection, |
|
277 int16_t aReason) |
|
278 { |
|
279 bool causedByComposition = IsEditorHandlingEventForComposition(); |
|
280 if (causedByComposition && |
|
281 !mUpdatePreference.WantChangesCausedByComposition()) { |
|
282 return NS_OK; |
|
283 } |
|
284 |
|
285 int32_t count = 0; |
|
286 nsresult rv = aSelection->GetRangeCount(&count); |
|
287 NS_ENSURE_SUCCESS(rv, rv); |
|
288 if (count > 0 && mWidget) { |
|
289 nsContentUtils::AddScriptRunner( |
|
290 new SelectionChangeEvent(this, causedByComposition)); |
|
291 } |
|
292 return NS_OK; |
|
293 } |
|
294 |
|
295 // Helper class, used for position change notification |
|
296 class PositionChangeEvent MOZ_FINAL : public nsRunnable |
|
297 { |
|
298 public: |
|
299 PositionChangeEvent(IMEContentObserver* aDispatcher) |
|
300 : mDispatcher(aDispatcher) |
|
301 { |
|
302 MOZ_ASSERT(mDispatcher); |
|
303 } |
|
304 |
|
305 NS_IMETHOD Run() |
|
306 { |
|
307 if (mDispatcher->GetWidget()) { |
|
308 mDispatcher->GetWidget()->NotifyIME( |
|
309 IMENotification(NOTIFY_IME_OF_POSITION_CHANGE)); |
|
310 } |
|
311 return NS_OK; |
|
312 } |
|
313 |
|
314 private: |
|
315 nsRefPtr<IMEContentObserver> mDispatcher; |
|
316 }; |
|
317 |
|
318 void |
|
319 IMEContentObserver::ScrollPositionChanged() |
|
320 { |
|
321 if (mWidget) { |
|
322 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); |
|
323 } |
|
324 } |
|
325 |
|
326 NS_IMETHODIMP |
|
327 IMEContentObserver::Reflow(DOMHighResTimeStamp aStart, |
|
328 DOMHighResTimeStamp aEnd) |
|
329 { |
|
330 if (mWidget) { |
|
331 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); |
|
332 } |
|
333 return NS_OK; |
|
334 } |
|
335 |
|
336 NS_IMETHODIMP |
|
337 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart, |
|
338 DOMHighResTimeStamp aEnd) |
|
339 { |
|
340 if (mWidget) { |
|
341 nsContentUtils::AddScriptRunner(new PositionChangeEvent(this)); |
|
342 } |
|
343 return NS_OK; |
|
344 } |
|
345 |
|
346 // Helper class, used for text change notification |
|
347 class TextChangeEvent : public nsRunnable |
|
348 { |
|
349 public: |
|
350 TextChangeEvent(IMEContentObserver* aDispatcher, |
|
351 uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd, |
|
352 bool aCausedByComposition) |
|
353 : mDispatcher(aDispatcher) |
|
354 , mStart(aStart) |
|
355 , mOldEnd(aOldEnd) |
|
356 , mNewEnd(aNewEnd) |
|
357 , mCausedByComposition(aCausedByComposition) |
|
358 { |
|
359 MOZ_ASSERT(mDispatcher); |
|
360 } |
|
361 |
|
362 NS_IMETHOD Run() |
|
363 { |
|
364 if (mDispatcher->GetWidget()) { |
|
365 IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); |
|
366 notification.mTextChangeData.mStartOffset = mStart; |
|
367 notification.mTextChangeData.mOldEndOffset = mOldEnd; |
|
368 notification.mTextChangeData.mNewEndOffset = mNewEnd; |
|
369 notification.mTextChangeData.mCausedByComposition = mCausedByComposition; |
|
370 mDispatcher->GetWidget()->NotifyIME(notification); |
|
371 } |
|
372 return NS_OK; |
|
373 } |
|
374 |
|
375 private: |
|
376 nsRefPtr<IMEContentObserver> mDispatcher; |
|
377 uint32_t mStart, mOldEnd, mNewEnd; |
|
378 bool mCausedByComposition; |
|
379 }; |
|
380 |
|
381 void |
|
382 IMEContentObserver::CharacterDataChanged(nsIDocument* aDocument, |
|
383 nsIContent* aContent, |
|
384 CharacterDataChangeInfo* aInfo) |
|
385 { |
|
386 NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), |
|
387 "character data changed for non-text node"); |
|
388 |
|
389 bool causedByComposition = IsEditorHandlingEventForComposition(); |
|
390 if (causedByComposition && |
|
391 !mUpdatePreference.WantChangesCausedByComposition()) { |
|
392 return; |
|
393 } |
|
394 |
|
395 uint32_t offset = 0; |
|
396 // get offsets of change and fire notification |
|
397 nsresult rv = |
|
398 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent, |
|
399 aInfo->mChangeStart, |
|
400 &offset, |
|
401 LINE_BREAK_TYPE_NATIVE); |
|
402 NS_ENSURE_SUCCESS_VOID(rv); |
|
403 |
|
404 uint32_t oldEnd = offset + aInfo->mChangeEnd - aInfo->mChangeStart; |
|
405 uint32_t newEnd = offset + aInfo->mReplaceLength; |
|
406 |
|
407 nsContentUtils::AddScriptRunner( |
|
408 new TextChangeEvent(this, offset, oldEnd, newEnd, causedByComposition)); |
|
409 } |
|
410 |
|
411 void |
|
412 IMEContentObserver::NotifyContentAdded(nsINode* aContainer, |
|
413 int32_t aStartIndex, |
|
414 int32_t aEndIndex) |
|
415 { |
|
416 bool causedByComposition = IsEditorHandlingEventForComposition(); |
|
417 if (causedByComposition && |
|
418 !mUpdatePreference.WantChangesCausedByComposition()) { |
|
419 return; |
|
420 } |
|
421 |
|
422 uint32_t offset = 0; |
|
423 nsresult rv = |
|
424 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer, |
|
425 aStartIndex, &offset, |
|
426 LINE_BREAK_TYPE_NATIVE); |
|
427 NS_ENSURE_SUCCESS_VOID(rv); |
|
428 |
|
429 // get offset at the end of the last added node |
|
430 nsIContent* childAtStart = aContainer->GetChildAt(aStartIndex); |
|
431 uint32_t addingLength = 0; |
|
432 rv = ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer, |
|
433 aEndIndex, &addingLength, |
|
434 LINE_BREAK_TYPE_NATIVE); |
|
435 NS_ENSURE_SUCCESS_VOID(rv); |
|
436 |
|
437 if (!addingLength) { |
|
438 return; |
|
439 } |
|
440 |
|
441 nsContentUtils::AddScriptRunner( |
|
442 new TextChangeEvent(this, offset, offset, offset + addingLength, |
|
443 causedByComposition)); |
|
444 } |
|
445 |
|
446 void |
|
447 IMEContentObserver::ContentAppended(nsIDocument* aDocument, |
|
448 nsIContent* aContainer, |
|
449 nsIContent* aFirstNewContent, |
|
450 int32_t aNewIndexInContainer) |
|
451 { |
|
452 NotifyContentAdded(aContainer, aNewIndexInContainer, |
|
453 aContainer->GetChildCount()); |
|
454 } |
|
455 |
|
456 void |
|
457 IMEContentObserver::ContentInserted(nsIDocument* aDocument, |
|
458 nsIContent* aContainer, |
|
459 nsIContent* aChild, |
|
460 int32_t aIndexInContainer) |
|
461 { |
|
462 NotifyContentAdded(NODE_FROM(aContainer, aDocument), |
|
463 aIndexInContainer, aIndexInContainer + 1); |
|
464 } |
|
465 |
|
466 void |
|
467 IMEContentObserver::ContentRemoved(nsIDocument* aDocument, |
|
468 nsIContent* aContainer, |
|
469 nsIContent* aChild, |
|
470 int32_t aIndexInContainer, |
|
471 nsIContent* aPreviousSibling) |
|
472 { |
|
473 bool causedByComposition = IsEditorHandlingEventForComposition(); |
|
474 if (causedByComposition && |
|
475 !mUpdatePreference.WantChangesCausedByComposition()) { |
|
476 return; |
|
477 } |
|
478 |
|
479 uint32_t offset = 0; |
|
480 nsresult rv = |
|
481 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, |
|
482 NODE_FROM(aContainer, |
|
483 aDocument), |
|
484 aIndexInContainer, &offset, |
|
485 LINE_BREAK_TYPE_NATIVE); |
|
486 NS_ENSURE_SUCCESS_VOID(rv); |
|
487 |
|
488 // get offset at the end of the deleted node |
|
489 int32_t nodeLength = |
|
490 aChild->IsNodeOfType(nsINode::eTEXT) ? |
|
491 static_cast<int32_t>(aChild->TextLength()) : |
|
492 std::max(static_cast<int32_t>(aChild->GetChildCount()), 1); |
|
493 MOZ_ASSERT(nodeLength >= 0, "The node length is out of range"); |
|
494 uint32_t textLength = 0; |
|
495 rv = ContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild, |
|
496 nodeLength, &textLength, |
|
497 LINE_BREAK_TYPE_NATIVE); |
|
498 NS_ENSURE_SUCCESS_VOID(rv); |
|
499 |
|
500 if (!textLength) { |
|
501 return; |
|
502 } |
|
503 |
|
504 nsContentUtils::AddScriptRunner( |
|
505 new TextChangeEvent(this, offset, offset + textLength, offset, |
|
506 causedByComposition)); |
|
507 } |
|
508 |
|
509 static nsIContent* |
|
510 GetContentBR(dom::Element* aElement) |
|
511 { |
|
512 if (!aElement->IsNodeOfType(nsINode::eCONTENT)) { |
|
513 return nullptr; |
|
514 } |
|
515 nsIContent* content = static_cast<nsIContent*>(aElement); |
|
516 return content->IsHTML(nsGkAtoms::br) ? content : nullptr; |
|
517 } |
|
518 |
|
519 void |
|
520 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument, |
|
521 dom::Element* aElement, |
|
522 int32_t aNameSpaceID, |
|
523 nsIAtom* aAttribute, |
|
524 int32_t aModType) |
|
525 { |
|
526 nsIContent *content = GetContentBR(aElement); |
|
527 mPreAttrChangeLength = content ? |
|
528 ContentEventHandler::GetNativeTextLength(content) : 0; |
|
529 } |
|
530 |
|
531 void |
|
532 IMEContentObserver::AttributeChanged(nsIDocument* aDocument, |
|
533 dom::Element* aElement, |
|
534 int32_t aNameSpaceID, |
|
535 nsIAtom* aAttribute, |
|
536 int32_t aModType) |
|
537 { |
|
538 bool causedByComposition = IsEditorHandlingEventForComposition(); |
|
539 if (causedByComposition && |
|
540 !mUpdatePreference.WantChangesCausedByComposition()) { |
|
541 return; |
|
542 } |
|
543 |
|
544 nsIContent *content = GetContentBR(aElement); |
|
545 if (!content) { |
|
546 return; |
|
547 } |
|
548 |
|
549 uint32_t postAttrChangeLength = |
|
550 ContentEventHandler::GetNativeTextLength(content); |
|
551 if (postAttrChangeLength == mPreAttrChangeLength) { |
|
552 return; |
|
553 } |
|
554 uint32_t start; |
|
555 nsresult rv = |
|
556 ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content, |
|
557 0, &start, |
|
558 LINE_BREAK_TYPE_NATIVE); |
|
559 NS_ENSURE_SUCCESS_VOID(rv); |
|
560 |
|
561 nsContentUtils::AddScriptRunner( |
|
562 new TextChangeEvent(this, start, start + mPreAttrChangeLength, |
|
563 start + postAttrChangeLength, causedByComposition)); |
|
564 } |
|
565 |
|
566 } // namespace mozilla |