/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Note that we don't need to add mFirstAddedContainer nor // mLastAddedContainer to cycle collection because they are non-null only // during short time and shouldn't be touched while they are non-null.
IMEContentObserver::IMEContentObserver() { #ifdef DEBUG // TODO: Make this test as GTest.
mTextChangeData.Test(); #endif
}
void IMEContentObserver::Init(nsIWidget& aWidget, nsPresContext& aPresContext,
Element* aElement, EditorBase& aEditorBase) {
State state = GetState(); if (NS_WARN_IF(state == eState_Observing)) { return; // Nothing to do.
}
bool firstInitialization = state != eState_StoppedObserving; if (!firstInitialization) { // If this is now trying to initialize with new contents, all observers // should be registered again for simpler implementation.
UnregisterObservers();
Clear();
}
if (!InitWithEditor(aPresContext, aElement, aEditorBase)) {
MOZ_LOG(sIMECOLog, LogLevel::Error,
("0x%p Init() FAILED, due to InitWithEditor() " "failure", this));
Clear(); return;
}
if (firstInitialization) { // Now, try to send NOTIFY_IME_OF_FOCUS to IME via the widget.
MaybeNotifyIMEOfFocusSet(); // When this is called first time, IME has not received NOTIFY_IME_OF_FOCUS // yet since NOTIFY_IME_OF_FOCUS will be sent to widget asynchronously. // So, we need to do nothing here. After NOTIFY_IME_OF_FOCUS has been // sent, OnIMEReceivedFocus() will be called and content, selection and/or // position changes will be observed return;
}
// When this is called after editor reframing (i.e., the root editable node // is also recreated), IME has usually received NOTIFY_IME_OF_FOCUS. In this // case, we need to restart to observe content, selection and/or position // changes in new root editable node.
ObserveEditableNode();
if (!NeedsToNotifyIMEOfSomething()) { return;
}
// Some change events may wait to notify IME because this was being // initialized. It is the time to flush them.
FlushMergeableNotifications();
}
void IMEContentObserver::OnIMEReceivedFocus() { // While Init() notifies IME of focus, pending layout may be flushed // because the notification may cause querying content. Then, recursive // call of Init() with the latest content may occur. In such case, we // shouldn't keep first initialization which notified IME of focus. if (GetState() != eState_Initializing) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p OnIMEReceivedFocus(), " "but the state is not \"initializing\", so does nothing", this)); return;
}
// NOTIFY_IME_OF_FOCUS might cause recreating IMEContentObserver // instance via IMEStateManager::UpdateIMEState(). So, this // instance might already have been destroyed, check it. if (!mRootElement) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p OnIMEReceivedFocus(), " "but mRootElement has already been cleared, so does nothing", this)); return;
}
// Start to observe which is needed by IME when IME actually has focus.
ObserveEditableNode();
if (!NeedsToNotifyIMEOfSomething()) { return;
}
// Some change events may wait to notify IME because this was being // initialized. It is the time to flush them.
FlushMergeableNotifications();
}
bool IMEContentObserver::InitWithEditor(nsPresContext& aPresContext,
Element* aElement,
EditorBase& aEditorBase) { // mEditableNode is one of // - Anonymous <div> in <input> or <textarea> // - Editing host if it's not in the design mode // - Document if it's in the design mode
mEditableNode = IMEStateManager::GetRootEditableNode(aPresContext, aElement); if (NS_WARN_IF(!mEditableNode)) { returnfalse;
}
// get selection and root content
nsCOMPtr<nsISelectionController> selCon; if (mEditableNode->IsContent()) {
nsIFrame* frame = mEditableNode->AsContent()->GetPrimaryFrame(); if (NS_WARN_IF(!frame)) { returnfalse;
}
frame->GetSelectionController(&aPresContext, getter_AddRefs(selCon));
} else { // mEditableNode is a document
selCon = presShell;
}
if (NS_WARN_IF(!selCon)) { returnfalse;
}
mSelection = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); if (NS_WARN_IF(!mSelection)) { returnfalse;
}
if (mEditorBase->IsTextEditor()) {
mRootElement = mEditorBase->GetRoot(); // The anonymous <div>
MOZ_ASSERT(mRootElement);
MOZ_ASSERT(mRootElement->GetFirstChild()); if (auto* text = Text::FromNodeOrNull(
mRootElement ? mRootElement->GetFirstChild() : nullptr)) {
mTextControlValueLength = ContentEventHandler::GetNativeTextLength(*text);
}
mIsTextControl = true;
} elseif (const nsRange* selRange = mSelection->GetRangeAt(0)) {
MOZ_ASSERT(!mIsTextControl); if (NS_WARN_IF(!selRange->GetStartContainer())) { returnfalse;
}
// If an editing host has focus, mRootElement is it. // Otherwise, if we're in the design mode, mRootElement is the <body> if // there is and startContainer is not outside of the <body>. Otherwise, the // document element is used instead.
nsCOMPtr<nsINode> startContainer = selRange->GetStartContainer();
mRootElement = Element::FromNodeOrNull(
startContainer->GetSelectionRootContent(presShell));
} else {
MOZ_ASSERT(!mIsTextControl); // If an editing host has focus, mRootElement is it. // Otherwise, if we're in the design mode, mRootElement is the <body> if // there is. Otherwise, the document element is used instead.
nsCOMPtr<nsINode> editableNode = mEditableNode;
mRootElement = Element::FromNodeOrNull(
editableNode->GetSelectionRootContent(presShell));
} if (!mRootElement && mEditableNode->IsDocument()) { // The document node is editable, but there are no contents, this document // is not editable. returnfalse;
}
if (NS_WARN_IF(!mRootElement)) { returnfalse;
}
mDocShell = aPresContext.GetDocShell(); if (NS_WARN_IF(!mDocShell)) { returnfalse;
}
mDocumentObserver = new DocumentObserver(*this);
returntrue;
}
void IMEContentObserver::Clear() {
mEditorBase = nullptr;
mSelection = nullptr;
mEditableNode = nullptr;
mRootElement = nullptr;
mDocShell = nullptr; // Should be safe to clear mDocumentObserver here even though it grabs // this instance in most cases because this is called by Init() or Destroy(). // The callers of Init() grab this instance with local RefPtr. // The caller of Destroy() also grabs this instance with local RefPtr. // So, this won't cause refcount of this instance become 0.
mDocumentObserver = nullptr;
}
// If this is called before sending NOTIFY_IME_OF_FOCUS (it's possible when // the editor is reframed before sending NOTIFY_IME_OF_FOCUS asynchronously), // the notification requests of mWidget may be different from after the widget // receives NOTIFY_IME_OF_FOCUS. So, this should be called again by // OnIMEReceivedFocus() which is called after sending NOTIFY_IME_OF_FOCUS. if (!mIMEHasFocus) {
MOZ_ASSERT(!mWidget || mNeedsToNotifyIMEOfFocusSet ||
mSendingNotification == NOTIFY_IME_OF_FOCUS, "Wow, OnIMEReceivedFocus() won't be called?"); return;
}
mIsObserving = true; if (mEditorBase) {
mEditorBase->SetIMEContentObserver(this);
}
mRootElement->AddMutationObserver(this); // If it's in a document (should be so), we can use document observer to // reduce redundant computation of text change offsets.
Document* doc = mRootElement->GetComposedDoc(); if (doc) {
RefPtr<DocumentObserver> documentObserver = mDocumentObserver;
documentObserver->Observe(doc);
}
if (mDocShell) { // Add scroll position listener and reflow observer to detect position // and size changes
mDocShell->AddWeakScrollObserver(this);
mDocShell->AddWeakReflowObserver(this);
}
}
void IMEContentObserver::NotifyIMEOfBlur() { // Prevent any notifications to be sent IME.
nsCOMPtr<nsIWidget> widget;
mWidget.swap(widget);
mIMENotificationRequests = nullptr;
// If we hasn't been set focus, we shouldn't send blur notification to IME. if (!mIMEHasFocus) { return;
}
// mWidget must have been non-nullptr if IME has focus.
MOZ_RELEASE_ASSERT(widget);
// For now, we need to send blur notification in any condition because // we don't have any simple ways to send blur notification asynchronously. // After this call, Destroy() or Unlink() will stop observing the content // and forget everything. Therefore, if it's not safe to send notification // when script blocker is unlocked, we cannot send blur notification after // that and before next focus notification. // Anyway, as far as we know, IME doesn't try to query content when it loses // focus. So, this may not cause any problem.
mIMEHasFocus = false;
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p NotifyIMEOfBlur(), sent NOTIFY_IME_OF_BLUR", this));
}
void IMEContentObserver::UnregisterObservers() { if (!mIsObserving) { return;
}
void IMEContentObserver::Destroy() { // WARNING: When you change this method, you have to check Unlink() too.
// Note that don't send any notifications later from here. I.e., notify // IMEStateManager of the blur synchronously because IMEStateManager needs to // stop notifying the main process if this is requested by the main process.
NotifyIMEOfBlur();
UnregisterObservers();
Clear();
bool IMEContentObserver::IsObserving(const nsPresContext& aPresContext, const Element* aElement) const { if (GetState() != eState_Observing) { returnfalse;
} // If aElement is not a text control, aElement is an editing host or entire // the document is editable in the design mode. Therefore, return false if // we're observing an anonymous subtree of a text control. if (!aElement || !aElement->IsTextControlElement() ||
!static_cast<const TextControlElement*>(aElement)
->IsSingleLineTextControlOrTextArea()) { if (mIsTextControl) { returnfalse;
}
} // If aElement is a text control, return true if we're observing the anonymous // subtree of aElement. Therefore, return false if we're observing with // HTMLEditor. elseif (!mIsTextControl) { returnfalse;
} return IsObservingContent(aPresContext, aElement);
}
bool IMEContentObserver::IsObserving( const TextComposition& aTextComposition) const { if (GetState() != eState_Observing) { returnfalse;
}
nsPresContext* const presContext = aTextComposition.GetPresContext(); if (NS_WARN_IF(!presContext)) { returnfalse;
} if (presContext != GetPresContext()) { returnfalse; // observing different document
} auto* const elementHavingComposition =
Element::FromNodeOrNull(aTextComposition.GetEventTargetNode()); bool isObserving = IsObservingContent(*presContext, elementHavingComposition); #ifdef DEBUG if (isObserving) { if (mIsTextControl) {
MOZ_ASSERT(elementHavingComposition);
MOZ_ASSERT(elementHavingComposition->IsTextControlElement(), "Should've never started to observe non-text-control element"); // XXX Our fake focus move has not been implemented properly. So, the // following assertions may fail, but I don't like to make the failures // cause crash even in debug builds because it may block developers to // debug web-compat issues. On the other hand, it'd be nice if we can // detect the bug with automated tests. Therefore, the following // assertions are NS_ASSERTION.
NS_ASSERTION(static_cast<TextControlElement*>(elementHavingComposition)
->IsSingleLineTextControlOrTextArea(), "Should've stopped observing when the type is changed");
NS_ASSERTION(!elementHavingComposition->IsInDesignMode(), "Should've stopped observing when the design mode started");
} elseif (elementHavingComposition) {
NS_ASSERTION(
!elementHavingComposition->IsTextControlElement() ||
!static_cast<TextControlElement*>(elementHavingComposition)
->IsSingleLineTextControlOrTextArea(), "Should've never started to observe text-control element or " "stopped observing it when the type is changed");
} else {
MOZ_ASSERT(presContext->GetPresShell());
MOZ_ASSERT(presContext->GetPresShell()->GetDocument());
NS_ASSERTION(
presContext->GetPresShell()->GetDocument()->IsInDesignMode(), "Should be observing entire the document only in the design mode");
}
} #endif// #ifdef DEBUG return isObserving;
}
IMEContentObserver::State IMEContentObserver::GetState() const { if (!mSelection || !mRootElement || !mEditableNode) { return eState_NotObserving; // failed to initialize or finalized.
} if (!mRootElement->IsInComposedDoc()) { // the focused editor has already been reframed. return eState_StoppedObserving;
} return mIsObserving ? eState_Observing : eState_Initializing;
}
bool IMEContentObserver::IsEditorComposing() const { // Note that don't use TextComposition here. The important thing is, // whether the editor already started to handle composition because // web contents can change selection, text content and/or something from // compositionstart event listener which is run before EditorBase handles it. if (NS_WARN_IF(!mEditorBase)) { returnfalse;
} return mEditorBase->IsIMEComposing();
}
nsresult IMEContentObserver::HandleQueryContentEvent(
WidgetQueryContentEvent* aEvent) { // If the instance has normal selection cache and the query event queries // normal selection's range, it should use the cached selection which was // sent to the widget. However, if this instance has already received new // selection change notification but hasn't updated the cache yet (i.e., // not sending selection change notification to IME, don't use the cached // value. Note that don't update selection cache here since if you update // selection cache here, IMENotificationSender won't notify IME of selection // change because it looks like that the selection isn't actually changed. constbool isSelectionCacheAvailable = aEvent->mUseNativeLineBreak &&
mSelectionData.IsInitialized() &&
!mNeedsToNotifyIMEOfSelectionChange; if (isSelectionCacheAvailable && aEvent->mMessage == eQuerySelectedText &&
aEvent->mInput.mSelectionType == SelectionType::eNormal) {
aEvent->EmplaceReply(); if (mSelectionData.HasRange()) {
aEvent->mReply->mOffsetAndData.emplace(mSelectionData.mOffset,
mSelectionData.String(),
OffsetAndDataFor::SelectedString);
aEvent->mReply->mReversed = mSelectionData.mReversed;
}
aEvent->mReply->mContentsRoot = mRootElement;
aEvent->mReply->mWritingMode = mSelectionData.GetWritingMode(); // The selection cache in IMEContentObserver must always have been in // an editing host (or an editable anonymous <div> element). Therefore, // we set mIsEditableContent to true here even though it's already been // blurred or changed its editable state but the selection cache has not // been invalidated yet.
aEvent->mReply->mIsEditableContent = true;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p HandleQueryContentEvent(aEvent={ " "mMessage=%s, mReply=%s })", this, ToChar(aEvent->mMessage), ToString(aEvent->mReply).c_str())); return NS_OK;
}
// If we can make the event's input offset absolute with TextComposition or // mSelection, we should set it here for reducing the cost of computing // selection start offset. If ContentEventHandler receives a // WidgetQueryContentEvent whose input offset is relative to insertion point, // it computes current selection start offset (this may be expensive) and // make the offset absolute value itself. // Note that calling MakeOffsetAbsolute() makes the event a query event with // absolute offset. So, ContentEventHandler doesn't pay any additional cost // after calling MakeOffsetAbsolute() here. if (aEvent->mInput.mRelativeToInsertionPoint &&
aEvent->mInput.IsValidEventMessage(aEvent->mMessage)) {
RefPtr<TextComposition> composition =
IMEStateManager::GetTextCompositionFor(aEvent->mWidget); if (composition) {
uint32_t compositionStart = composition->NativeOffsetOfStartComposition(); if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(compositionStart))) { return NS_ERROR_FAILURE;
}
} elseif (isSelectionCacheAvailable && mSelectionData.HasRange()) { const uint32_t selectionStart = mSelectionData.mOffset; if (NS_WARN_IF(!aEvent->mInput.MakeOffsetAbsolute(selectionStart))) { return NS_ERROR_FAILURE;
}
}
}
AutoRestore<bool> handling(mIsHandlingQueryContentEvent);
mIsHandlingQueryContentEvent = true;
ContentEventHandler handler(GetPresContext());
nsresult rv = handler.HandleQueryContentEvent(aEvent); if (NS_WARN_IF(Destroyed())) { // If this has already destroyed during querying the content, the query // is outdated even if it's succeeded. So, make the query fail.
aEvent->mReply.reset();
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p HandleQueryContentEvent(), WARNING, " "IMEContentObserver has been destroyed during the query, " "making the query fail", this)); return rv;
}
if (aEvent->Succeeded() &&
NS_WARN_IF(aEvent->mReply->mContentsRoot != mRootElement)) { // Focus has changed unexpectedly, so make the query fail.
aEvent->mReply.reset();
} return rv;
}
nsresult IMEContentObserver::MaybeHandleSelectionEvent(
nsPresContext* aPresContext, WidgetSelectionEvent* aEvent) {
MOZ_ASSERT(aEvent);
MOZ_ASSERT(aEvent->mMessage == eSetSelection);
NS_ASSERTION(!mNeedsToNotifyIMEOfSelectionChange, "Selection cache has not been updated yet");
// When we have Selection cache, and the caller wants to set same selection // range, we shouldn't try to compute same range because it may be impossible // if the range boundary is around element boundaries which won't be // serialized with line breaks like close tags of inline elements. In that // case, inserting new text at different point may be different from intention // of users or web apps which set current selection. // FIXME: We cache only selection data computed with native line breaker // lengths. Perhaps, we should improve the struct to have both data of // offset and length. E.g., adding line break counts for both offset and // length. if (!mNeedsToNotifyIMEOfSelectionChange && aEvent->mUseNativeLineBreak &&
mSelectionData.IsInitialized() && mSelectionData.HasRange() &&
mSelectionData.StartOffset() == aEvent->mOffset &&
mSelectionData.Length() == aEvent->mLength) { if (RefPtr<Selection> selection = mSelection) {
selection->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION);
}
aEvent->mSucceeded = true; return NS_OK;
}
bool IMEContentObserver::OnMouseButtonEvent(nsPresContext& aPresContext,
WidgetMouseEvent& aMouseEvent) { if (!mIMENotificationRequests ||
!mIMENotificationRequests->WantMouseButtonEventOnChar()) { returnfalse;
} if (!aMouseEvent.IsTrusted() || aMouseEvent.DefaultPrevented() ||
!aMouseEvent.mWidget) { returnfalse;
} // Now, we need to notify only mouse down and mouse up event. switch (aMouseEvent.mMessage) { case eMouseUp: case eMouseDown: break; default: returnfalse;
} if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) { returnfalse;
}
// The widget might be destroyed during querying the content since it // causes flushing layout. if (!mWidget || NS_WARN_IF(mWidget->Destroyed())) { returnfalse;
}
// The result character rect is relative to the top level widget. // We should notify it with offset in the widget.
nsIWidget* topLevelWidget = mWidget->GetTopLevelWidget(); if (topLevelWidget && topLevelWidget != mWidget) {
queryCharAtPointEvent.mReply->mRect.MoveBy(
topLevelWidget->WidgetToScreenOffset() -
mWidget->WidgetToScreenOffset());
} // The refPt is relative to its widget. // We should notify it with offset in the widget. if (aMouseEvent.mWidget != mWidget) {
queryCharAtPointEvent.mRefPoint +=
aMouseEvent.mWidget->WidgetToScreenOffset() -
mWidget->WidgetToScreenOffset();
}
// Although we don't assume this change occurs while this is storing // the range of added consecutive nodes, if it actually happens, we need to // flush them since this change may occur before or in the range. So, it's // safe to flush pending computation of mTextChangeData before handling this. if (mAddedContentCache.HasCache()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
mPreCharacterDataChangeLength = ContentEventHandler::GetNativeTextLength(
*aContent->AsText(), aInfo.mChangeStart, aInfo.mChangeEnd);
MOZ_ASSERT(
mPreCharacterDataChangeLength >= aInfo.mChangeEnd - aInfo.mChangeStart, "The computed length must be same as or larger than XP length");
}
void IMEContentObserver::CharacterDataChanged(
nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { if (!aContent->IsText()) { return; // Ignore if it's a comment node or something other invisible data // node.
}
// Let TextComposition have a change to update composition string range in // the text node if the change is caused by the web apps. if (mWidget && !IsEditorHandlingEventForComposition()) { if (RefPtr<TextComposition> composition =
IMEStateManager::GetTextCompositionFor(mWidget)) {
composition->OnCharacterDataChanged(*aContent->AsText(), aInfo);
}
}
if (!NeedsTextChangeNotification() ||
!nsContentUtils::IsInSameAnonymousTree(mRootElement, aContent)) { return;
}
if (mAddedContentCache.HasCache()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
MOZ_ASSERT(
!mAddedContentCache.HasCache(), "The stored range should be flushed before actually the data is changed");
MOZ_ASSERT(removedLength >= 0, "mPreCharacterDataChangeLength should've been set by " "CharacterDataWillChange()");
uint32_t offset = 0; if (mIsTextControl) { // If we're observing a text control, mRootElement is the anonymous <div> // element which has only one text node and/or invisible <br> element. // TextEditor assumes this structure when it handles editing commands. // Therefore, it's safe to assume same things here.
MOZ_ASSERT(mRootElement->GetFirstChild() == aContent); if (aInfo.mChangeStart) {
offset = ContentEventHandler::GetNativeTextLength(*aContent->AsText(), 0,
aInfo.mChangeStart);
}
} else {
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::BeforeFirstContentOf(*mRootElement),
RawNodePosition(aContent, aInfo.mChangeStart), mRootElement, &offset,
LINE_BREAK_TYPE_NATIVE); if (NS_WARN_IF(NS_FAILED(rv))) { return;
}
}
// We can skip everything when a padding <br> element is added since its text // length is 0. if (aFirstContent == aLastContent) { if (constauto* brElement = HTMLBRElement::FromNode(aFirstContent)) { if (MOZ_LIKELY(!brElement->HasChildNodes()) &&
(brElement->IsPaddingForEmptyEditor() ||
brElement->IsPaddingForEmptyLastLine())) { return;
}
}
}
// While a document change, new nodes should be added consecutively in a // container node. Therefore, we can cache the first added node and the last // added node until ending the document change at least. Then, we can avoid // to compute first added node offset in the flattened text repeatedly. bool needToCache = true; if (mAddedContentCache.HasCache()) {
MOZ_DIAGNOSTIC_ASSERT(aFirstContent->GetParentNode() ==
aLastContent->GetParentNode()); if (mAddedContentCache.IsInRange(*aFirstContent, mRootElement)) { // The new content nodes are in the range, we can include their text // length when we flush the cached range later. Therefore, we need to // do nothing in this case.
needToCache = false;
MOZ_LOG(sCacheLog, LogLevel::Info,
("ContentAdded: mAddedContentCache already caches the give " "content nodes"));
MOZ_ASSERT(mAddedContentCache.IsInRange(*aLastContent, mRootElement));
} // When new nodes are inserted in a different container, let's flush the // preceding content first. Then, we should restart to cache the new // inserted nodes. elseif (!mAddedContentCache.CanMergeWith(*aFirstContent, *aLastContent,
mRootElement)) {
MOZ_LOG(sCacheLog, LogLevel::Info,
("ContentAdded: mAddedContentCache was cached not in current " "document change and new content nodes cannot be merged"));
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
OffsetAndLengthAdjustments differences;
Result<std::pair<uint32_t, uint32_t>, nsresult> offsetAndLength =
mAddedContentCache.ComputeFlatTextRangeBeforeInsertingNewContent(
*aFirstContent, *aLastContent, mRootElement, differences); if (NS_WARN_IF(offsetAndLength.isErr())) {
MOZ_LOG(sCacheLog, LogLevel::Error,
("ContentAdded: " "AddedContentCache::" "ComputeFlatTextRangeExcludingInsertingNewContent() failed"));
mAddedContentCache.Clear(__FUNCTION__); return;
}
NotifyIMEOfCachedConsecutiveNewNodes(
__FUNCTION__, Some(offsetAndLength.inspect().first),
Some(offsetAndLength.inspect().second), differences);
mAddedContentCache.Clear(__FUNCTION__);
}
}
// Okay, now, we can start to cache new nodes or merge the range of new // nodes with the cached range. if (!mAddedContentCache.TryToCache(*aFirstContent, *aLastContent,
mRootElement)) { // Flush the old range first.
MOZ_LOG(sCacheLog, LogLevel::Info,
("ContentAdded: called during a document change flushed " "previous added nodes (aFirstContent=%s, aLastContent=%s)",
ToString(RefPtr<nsINode>(aFirstContent)).c_str(),
ToString(RefPtr<nsINode>(aLastContent)).c_str()));
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
MOZ_ASSERT(!mAddedContentCache.HasCache());
MOZ_ALWAYS_TRUE(mAddedContentCache.TryToCache(*aFirstContent, *aLastContent,
mRootElement));
}
}
// If 2 <div> elements are inserted into the DOM, we wan't the text length // from start of the first <div> (including line break caused by its open // tag) to end of the second <div>. I.e., we want to compute: // ...{<div>.....</div><div>......</div>}... // ^ ^ ^ ^ // | mFirst | | // | mLast | // offset (offset + length)
Maybe<uint32_t> offset =
aOffsetOfFirstContent.isSome()
? aOffsetOfFirstContent
: mEndOfAddedTextCache.GetFlatTextLengthBeforeContent(
*mAddedContentCache.mFirst, mRootElement); if (offset.isNothing()) {
Result<uint32_t, nsresult> textLengthBeforeFirstContentOrError =
FlatTextCache::ComputeTextLengthBeforeContent(
*mAddedContentCache.mFirst, mRootElement); if (NS_WARN_IF(textLengthBeforeFirstContentOrError.isErr())) {
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
MOZ_LOG(
sCacheLog, LogLevel::Error,
("NotifyContentAdded: failed to compute text length before mFirst"));
mAddedContentCache.Clear(__FUNCTION__); return;
}
offset = Some(textLengthBeforeFirstContentOrError.unwrap());
}
Maybe<uint32_t> length = aLengthOfContentNNodes; if (aLengthOfContentNNodes.isNothing()) {
Result<uint32_t, nsresult> addingLengthOrError =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
*mAddedContentCache.mFirst, *mAddedContentCache.mLast,
mRootElement); if (NS_WARN_IF(addingLengthOrError.isErr())) {
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
MOZ_LOG(sCacheLog, LogLevel::Error,
("NotifyContentAdded: failed to compute text length of added"));
mAddedContentCache.Clear(__FUNCTION__); return;
}
length = Some(addingLengthOrError.inspect());
}
// If multiple lines are being inserted in an HTML editor, next call of // NotifyContentAdded() is for adding next node. Therefore, caching the text // length can skip to compute the text length before the adding node and // before of it.
mEndOfAddedTextCache.CacheFlatTextLengthBeforeEndOfContent(
__FUNCTION__, *mAddedContentCache.mLast,
aAdjustments.AdjustedEndOffset(*offset + *length), mRootElement);
mStartOfRemovingTextRangeCache.ContentAdded(
__FUNCTION__, *mAddedContentCache.mFirst, *mAddedContentCache.mLast,
Some(aAdjustments.AdjustedEndOffset(*offset + *length)), mRootElement);
// We can skip everything when padding <br> element is removed since its text // length is 0. if (constauto* brElement = HTMLBRElement::FromNode(aChild)) { if (MOZ_LIKELY(!brElement->HasChildNodes()) &&
(brElement->IsPaddingForEmptyEditor() ||
brElement->IsPaddingForEmptyLastLine())) { return;
}
}
Maybe<uint32_t> offset =
mStartOfRemovingTextRangeCache.GetFlatTextLengthBeforeContent(
*aChild, mRootElement, ForRemoval::Yes);
nsIContent* const prevSibling = aChild->GetPreviousSibling(); if (offset.isSome()) { // Update the cache because next remove may be the previous or the next // sibling removal. So, caching offset of currently removing content node // makes us skip computing offset of next removal. if (prevSibling) {
mStartOfRemovingTextRangeCache.CacheFlatTextLengthBeforeEndOfContent(
__FUNCTION__, *prevSibling, *offset, mRootElement);
} else {
mStartOfRemovingTextRangeCache.CacheFlatTextLengthBeforeFirstContent(
__FUNCTION__, *containerNode, *offset, mRootElement);
}
} else { if (prevSibling) { // When we compute preceding text length of the removing content node, we // cannot make the range cross the removing node boundary because // containerNode->ComputeIndexOf(aChild) returns Nothing so that // ContentEventHandler fails to compute the length. Therefore, if a <div> // is being removed, we want to compute the length of `...}<div>`. if (NS_WARN_IF(
NS_FAILED(mStartOfRemovingTextRangeCache
.ComputeAndCacheFlatTextLengthBeforeEndOfContent(
__FUNCTION__, *prevSibling, mRootElement)))) { return;
}
} else { // At removing a child node of containerNode, we need the line break // caused by open tag of containerNode. if (NS_WARN_IF(
NS_FAILED(mStartOfRemovingTextRangeCache
.ComputeAndCacheFlatTextLengthBeforeFirstContent(
__FUNCTION__, *containerNode, mRootElement)))) { return;
}
}
offset = Some(mStartOfRemovingTextRangeCache.GetFlatTextLength());
}
// We do not need a text change notification since removing aChild does not // change flattened text and no pending added length. if (textLengthOrError.inspect() == 0u) { return;
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY void IMEContentObserver::ParentChainChanged(
nsIContent* aContent) { // When the observing element itself is directly removed from the document // without a focus move, i.e., it's the root of the removed document fragment // and the editor was handling the design mode, we have already stopped // observing the element because IMEStateManager::OnRemoveContent() should // have already been called for it and the instance which was observing the // node has already been destroyed. Therefore, this is called only when // this is observing the <body> in the design mode and it's disconnected from // the tree by an <html> element removal. Even in this case, IMEStateManager // never gets a focus change notification, but we need to notify IME of focus // change because we cannot interact with IME anymore due to no editable // content. Therefore, this method notifies IMEStateManager of the // disconnection of the observing node to emulate a blur from the editable // content.
MOZ_ASSERT(mIsObserving);
OwningNonNull<IMEContentObserver> observer(*this);
IMEStateManager::OnParentChainChangedOfObservingElement(observer);
}
MOZ_ASSERT(mTextChangeData.IsValid(), "mTextChangeData must have text change data");
mNeedsToNotifyIMEOfTextChange = true; // Even if the observer hasn't received selection change, selection in the // flat text may have already been changed. For example, when previous `<p>` // element of another `<p>` element which contains caret is removed by a DOM // mutation, selection change event won't be fired, but selection start offset // should be decreased by the length of removed `<p>` element. // In such case, HandleQueryContentEvent shouldn't use the selection cache // anymore. Therefore, we also need to post selection change notification // too. eQuerySelectedText event may be dispatched at sending a text change // notification.
mNeedsToNotifyIMEOfSelectionChange = true;
}
void IMEContentObserver::MaybeNotifyIMEOfPositionChange() {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p MaybeNotifyIMEOfPositionChange()", this)); // If reflow is caused by ContentEventHandler during PositionChangeEvent // sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it // again since ContentEventHandler returns the result including this reflow's // result. if (mIsHandlingQueryContentEvent &&
mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p MaybeNotifyIMEOfPositionChange(), ignored since caused by " "ContentEventHandler during sending NOTIFY_IME_OF_POSITION_CHANGE", this)); return;
}
PostPositionChangeNotification();
FlushMergeableNotifications();
}
// XXX Cannot we cache some information for reducing the cost to compute // selection offset and writing mode?
WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
mWidget);
querySelectedTextEvent.mNeedsToFlushLayout = aRequireFlush;
ContentEventHandler handler(GetPresContext());
handler.OnQuerySelectedText(&querySelectedTextEvent); if (NS_WARN_IF(querySelectedTextEvent.Failed()) ||
NS_WARN_IF(querySelectedTextEvent.mReply->mContentsRoot !=
mRootElement)) { returnfalse;
}
bool IMEContentObserver::IsReflowLocked() const {
nsPresContext* presContext = GetPresContext(); if (NS_WARN_IF(!presContext)) { returnfalse;
}
PresShell* presShell = presContext->GetPresShell(); if (NS_WARN_IF(!presShell)) { returnfalse;
} // During reflow, we shouldn't notify IME because IME may query content // synchronously. Then, it causes ContentEventHandler will try to flush // pending notifications during reflow. return presShell->IsReflowLocked();
}
bool IMEContentObserver::IsSafeToNotifyIME() const { // If this is already detached from the widget, this doesn't need to notify // anything. if (!mWidget) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no widget", this)); returnfalse;
}
// Don't notify IME of anything if it's not good time to do it. if (mSuppressNotifications) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no widget", this)); returnfalse;
}
if (!mESM || NS_WARN_IF(!GetPresContext())) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no " "EventStateManager and/or PresContext", this)); returnfalse;
}
// If it's in reflow, we should wait to finish the reflow. // FYI: This should be called again from Reflow() or ReflowInterruptible(). if (IsReflowLocked()) {
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of reflow locked", this)); returnfalse;
}
// If we're in handling an edit action, this method will be called later. if (EditorIsHandlingEditSubAction()) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of focused " "editor handling somethings", this)); returnfalse;
}
returntrue;
}
void IMEContentObserver::FlushMergeableNotifications() { if (!IsSafeToNotifyIME()) { // So, if this is already called, this should do nothing.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, do nothing due to " "unsafe to notify IME", this)); return;
}
// Notifying something may cause nested call of this method. For example, // when somebody notified one of the notifications may dispatch query content // event. Then, it causes flushing layout which may cause another layout // change notification.
if (mQueuedSender) { // So, if this is already called, this should do nothing.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, do nothing due to " "already flushing pending notifications", this)); return;
}
// If text change notification and/or position change notification becomes // unnecessary, let's cancel them. if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
CancelNotifyingIMEOfTextChange();
} if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
CancelNotifyingIMEOfPositionChange();
}
if (!NeedsToNotifyIMEOfSomething()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, due to no pending " "notifications", this)); return;
}
// NOTE: Reset each pending flag because sending notification may cause // another change.
// If contents in selection range is modified, the selection range still // has removed node from the tree. In such case, ContentIterator won't // work well. Therefore, we shouldn't use AddScriptRunner() here since // it may kick runnable event immediately after DOM tree is changed but // the selection range isn't modified yet.
mQueuedSender = new IMENotificationSender(this);
mQueuedSender->Dispatch(mDocShell);
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p FlushMergeableNotifications(), finished", this));
}
void IMEContentObserver::TryToFlushPendingNotifications(bool aAllowAsync) { // If a sender instance is sending notifications, we shouldn't try to create // a new sender again because the sender will recreate by itself if there are // new pending notifications. if (mSendingNotification != NOTIFY_IME_OF_NOTHING) { return;
}
// When the caller allows to put off notifying IME, we can wait the next // call of this method or to run the queued sender. if (mQueuedSender && XRE_IsContentProcess() && aAllowAsync) { return;
}
if (!mQueuedSender) { // If it was not safe to dispatch notifications when the pending // notifications are posted, this may not have IMENotificationSender // instance because it couldn't dispatch it, e.g., when an edit sub-action // is being handled in the editor, we shouldn't do it even if it's safe to // run script. Therefore, we need to create the sender instance here in the // case. if (!NeedsToNotifyIMEOfSomething()) { return;
}
mQueuedSender = new IMENotificationSender(this);
}
if (aChangeEventType == eChangeEventType_CompositionEventHandled) { if (observer->mWidget) { returntrue;
}
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of " "composition event handled because of no widget", this)); returnfalse;
}
State state = observer->GetState(); // If it's not initialized, we should do nothing. if (state == eState_NotObserving) {
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because " "of not observing", this)); returnfalse;
} // If setting focus, just check the state. if (aChangeEventType == eChangeEventType_Focus) { if (!observer->mIMEHasFocus) { returntrue;
}
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of focus " "change because of already focused", this));
NS_WARNING("IME already has focus"); returnfalse;
} // If we've not notified IME of focus yet, we shouldn't notify anything. if (!observer->mIMEHasFocus) {
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because " "of not focused", this)); returnfalse;
}
// If IME has focus, IMEContentObserver must hold the widget.
MOZ_ASSERT(observer->mWidget);
if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " "IME because of not safe to run script", this)); returnfalse;
}
RefPtr<IMEContentObserver> observer = GetObserver(); if (!observer) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " "IME because of no observer", this)); returnfalse;
}
// While we're sending a notification, we shouldn't send another notification // recursively. if (observer->mSendingNotification != NOTIFY_IME_OF_NOTHING) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " "IME because of the observer sending another notification", this)); returnfalse;
}
State state = observer->GetState(); if (aChangeEventType == eChangeEventType_Focus) { if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot " "notify IME of focus because of not observing", this)); returnfalse;
}
} elseif (aChangeEventType == eChangeEventType_CompositionEventHandled) { // It doesn't need to check the observing status.
} elseif (state != eState_Observing) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify " "IME because of not observing", this)); returnfalse;
} return observer->IsSafeToNotifyIME();
}
void IMEContentObserver::IMENotificationSender::Dispatch(
nsIDocShell* aDocShell) { if (XRE_IsContentProcess() && aDocShell) { if (RefPtr<nsPresContext> presContext = aDocShell->GetPresContext()) { if (nsRefreshDriver* refreshDriver = presContext->RefreshDriver()) {
refreshDriver->AddEarlyRunner(this); return;
}
}
}
NS_DispatchToCurrentThread(this);
}
NS_IMETHODIMP
IMEContentObserver::IMENotificationSender::Run() { if (NS_WARN_IF(mIsRunning)) {
MOZ_LOG(
sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::Run(), FAILED, due to called recursively", this)); return NS_OK;
}
RefPtr<IMEContentObserver> observer = GetObserver(); if (!observer) { return NS_OK;
}
// This instance was already performed forcibly. if (observer->mQueuedSender != this) { return NS_OK;
}
// NOTE: Reset each pending flag because sending notification may cause // another change.
if (observer->mNeedsToNotifyIMEOfFocusSet) {
observer->mNeedsToNotifyIMEOfFocusSet = false;
SendFocusSet();
observer->mQueuedSender = nullptr; // If it's not safe to notify IME of focus, SendFocusSet() sets // mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the // focus notification later, we should put a new sender into the queue but // this case must be rare. Note that if mIMEContentObserver is already // destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again. if (observer->mNeedsToNotifyIMEOfFocusSet) {
MOZ_ASSERT(!observer->mIMEHasFocus);
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), posting " "IMENotificationSender to current thread", this));
observer->mQueuedSender = new IMENotificationSender(observer);
observer->mQueuedSender->Dispatch(observer->mDocShell); return NS_OK;
} // This is the first notification to IME. So, we don't need to notify // anymore since IME starts to query content after it gets focus.
observer->ClearPendingNotifications(); return NS_OK;
}
if (observer->mNeedsToNotifyIMEOfTextChange) {
observer->mNeedsToNotifyIMEOfTextChange = false;
SendTextChange();
}
// If a text change notification causes another text change again, we should // notify IME of that before sending a selection change notification. if (!observer->mNeedsToNotifyIMEOfTextChange) { // Be aware, PuppetWidget depends on the order of this. A selection change // notification should not be sent before a text change notification because // PuppetWidget shouldn't query new text content every selection change. if (observer->mNeedsToNotifyIMEOfSelectionChange) {
observer->mNeedsToNotifyIMEOfSelectionChange = false;
SendSelectionChange();
}
}
// If a text change notification causes another text change again or a // selection change notification causes either a text change or another // selection change, we should notify IME of those before sending a position // change notification. if (!observer->mNeedsToNotifyIMEOfTextChange &&
!observer->mNeedsToNotifyIMEOfSelectionChange) { if (observer->mNeedsToNotifyIMEOfPositionChange) {
observer->mNeedsToNotifyIMEOfPositionChange = false;
SendPositionChange();
}
}
// Composition event handled notification should be sent after all the // other notifications because this notifies widget of finishing all pending // events are handled completely. if (!observer->mNeedsToNotifyIMEOfTextChange &&
!observer->mNeedsToNotifyIMEOfSelectionChange &&
!observer->mNeedsToNotifyIMEOfPositionChange) { if (observer->mNeedsToNotifyIMEOfCompositionEventHandled) {
observer->mNeedsToNotifyIMEOfCompositionEventHandled = false;
SendCompositionEventHandled();
}
}
observer->mQueuedSender = nullptr;
// If notifications caused some new change, we should notify them now. if (observer->NeedsToNotifyIMEOfSomething()) { if (observer->GetState() == eState_StoppedObserving) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), waiting " "IMENotificationSender to be reinitialized", this));
} else {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), posting " "IMENotificationSender to current thread", this));
observer->mQueuedSender = new IMENotificationSender(observer);
observer->mQueuedSender->Dispatch(observer->mDocShell);
}
} return NS_OK;
}
if (!CanNotifyIME(eChangeEventType_Focus)) { // If IMEContentObserver has already gone, we don't need to notify IME of // focus.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendFocusSet(), Warning, does not " "send notification due to impossible to notify IME of focus", this));
observer->ClearPendingNotifications(); return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Focus)) {
MOZ_LOG(
sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendFocusSet(), Warning, does not send " "notification due to unsafe, retrying to send NOTIFY_IME_OF_FOCUS...", this));
observer->PostFocusSetNotification(); return;
}
observer->mIMEHasFocus = true; // Initialize selection cache with the first selection data. However, this // may be handled synchronously when the editor gets focus. In that case, // some frames may be dirty and they may be required to get caret frame in // ContentEventHandler::Init() to get the nearest widget from the selection. // Therefore, we need to update selection cache with flushing the pending // notifications.
observer->UpdateSelectionCache(true);
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendFocusSet(), sending " "NOTIFY_IME_OF_FOCUS...", this));
// IMENotificationRequests referred by ObserveEditableNode() may be different // before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need // to guarantee to call ObserveEditableNode() after sending // NOTIFY_IME_OF_FOCUS.
observer->OnIMEReceivedFocus();
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendFocusSet(), sent NOTIFY_IME_OF_FOCUS", this));
}
if (!CanNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendSelectionChange(), Warning, " "does not send notification due to impossible to notify IME of " "selection change", this)); return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendSelectionChange(), Warning, " "does not send notification due to unsafe, retrying to send " "NOTIFY_IME_OF_SELECTION_CHANGE...", this));
observer->PostSelectionChangeNotification(); return;
}
SelectionChangeData lastSelChangeData = observer->mSelectionData; if (NS_WARN_IF(!observer->UpdateSelectionCache())) {
MOZ_LOG(sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due " "to UpdateSelectionCache() failure", this)); return;
}
// The state may be changed since querying content causes flushing layout. if (!CanNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due " "to flushing layout having changed something", this)); return;
}
// If the selection isn't changed actually, we shouldn't notify IME of // selection change.
SelectionChangeData& newSelChangeData = observer->mSelectionData; if (lastSelChangeData.IsInitialized() &&
lastSelChangeData.EqualsRangeAndDirectionAndWritingMode(
newSelChangeData)) {
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendSelectionChange(), not notifying IME " "of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually", this)); return;
}
if (!CanNotifyIME(eChangeEventType_Text)) {
MOZ_LOG(
sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, does not " "send notification due to impossible to notify IME of text change", this)); return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Text)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, does " "not send notification due to unsafe, retrying to send " "NOTIFY_IME_OF_TEXT_CHANGE...", this));
observer->PostTextChangeNotification(); return;
}
// If text change notification is unnecessary anymore, just cancel it. if (!observer->NeedsTextChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, " "canceling sending NOTIFY_IME_OF_TEXT_CHANGE", this));
observer->CancelNotifyingIMEOfTextChange(); return;
}
if (!CanNotifyIME(eChangeEventType_Position)) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, " "does not send notification due to impossible to notify IME of " "position change", this)); return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Position)) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, " "does not send notification due to unsafe, retrying to send " "NOTIFY_IME_OF_POSITION_CHANGE...", this));
observer->PostPositionChangeNotification(); return;
}
// If position change notification is unnecessary anymore, just cancel it. if (!observer->NeedsPositionChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, " "canceling sending NOTIFY_IME_OF_POSITION_CHANGE", this));
observer->CancelNotifyingIMEOfPositionChange(); return;
}
if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendCompositionEventHandled(), " "Warning, does not send notification due to impossible to notify " "IME of composition event handled", this)); return;
}
if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendCompositionEventHandled(), " "Warning, does not send notification due to unsafe, retrying to " "send NOTIFY_IME_OF_POSITION_CHANGE...", this));
observer->PostCompositionEventHandledNotification(); return;
}
// Notify IMEContentObserver of ending of document updates if this already // notified it of beginning of document updates. for (; IsUpdating(); --mDocumentUpdating) { // FYI: IsUpdating() returns true until mDocumentUpdating becomes 0. // However, IsObserving() returns false now because mDocument was // already cleared above. Therefore, this method won't be called // recursively.
observer->EndDocumentUpdate();
}
}
nsIContent* const prevSibling = aContent.GetPreviousSibling(); if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent); // If aContent is the first child of mContainerNode and we're caching text // length before first child of mContainerNode, we're caching the result // as-is.. if (!prevSibling && mContainerNode == aContent.GetParentNode()) { return Some(mFlatTextLength);
} return Nothing();
}
// If we're caching text length before end of previous sibling of aContent, // the cached length is the result of this call. if (mContent == prevSibling) { return Some(mFlatTextLength);
}
// If we're caching text length before end of aContent, aContent siblings // may be being removed backward because aContent is the previous sibling of // previously removed node. We should return the length with computing the // text length of aContent because it's much faster than computing the length // starting from the root element especially when there are a lot of preceding // content. if (mContent == &aContent) { const Result<uint32_t, nsresult> textLength =
FlatTextCache::ComputeTextLengthOfContent(aContent, aRootElement,
aForRemoval); if (NS_WARN_IF(textLength.isErr()) ||
NS_WARN_IF(mFlatTextLength < textLength.inspect())) { return Nothing();
} return Some(mFlatTextLength - textLength.inspect());
} return Nothing();
}
if (!mContainerNode || mContainerNode != aFirstContent.GetParentNode()) { return Nothing();
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent); // If aFirstContent is the first child of mContainerNode, we're caching the // result as-is. if (mContainerNode->GetFirstChild() == &aFirstContent) { return Some(mFlatTextLength);
} return Nothing();
}
// When the content nodes are inserted forward, we may cache text length // before end of last inserted content. If so, mContent should be the // previous sibling of aFirstContent. Then, we can return the cached length // simply. if (mContent == aFirstContent.GetPreviousSibling()) { return Some(mFlatTextLength);
} // When the content nodes inserted backward, we may cache text length before // the end of the last inserted content which is next or latter sibling of // aLastContent. In this case, we can compute the length with the cache with // computing text length starting from the next sibling of aLastContent to // mContent which were previously inserted. That must be faster than // computing the length starting from the root element. if (mContent == aLastContent.GetNextSibling() ||
aLastContent.ComputeIndexInParentNode().valueOr(UINT32_MAX) <
mContent->ComputeIndexInParentNode().valueOr(0u)) {
Result<uint32_t, nsresult> previouslyInsertedTextLengthOrError =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
*aLastContent.GetNextSibling(), *mContent, aRootElement); if (NS_WARN_IF(previouslyInsertedTextLengthOrError.isErr()) ||
NS_WARN_IF(mFlatTextLength <
previouslyInsertedTextLengthOrError.inspect())) { return Nothing();
} // mFlatTextLength contains the last inserted text length, but it does not // contain text length starting from aFirstContent to aLastContent. // Therefore, subtracting the last inserted text length from mFlatTextLength // equals the text length before aFirstContent. return Some(mFlatTextLength - previouslyInsertedTextLengthOrError.unwrap());
} return Nothing();
}
if (const Text* textNode = Text::FromNode(aContent)) { return ContentEventHandler::GetNativeTextLength(*textNode);
}
if (aForRemoval == ForRemoval::Yes) { // When we compute the text length of the removing content node, we need to // select all children in the removing node because of the same reason // above. Therefore, if a <div> is being removed, we want to compute // `{<div>...}</div>`. In this case, we want to include the open tag of // aRemovingContent if it's an element to add the line break if it's caused // by the open tag. However, we have no way to specify it with // RawNodePosition, but ContentEventHandler::GetFlatTextLengthInRange() // treats the range as the start container is selected. Therefore, we // should use a RawNodePosition setting its container to the removed node.
uint32_t textLength = 0;
RawNodePosition start(const_cast<nsIContent*>(&aContent), 0u);
start.mAfterOpenTag = false;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
start, RawNodePosition::AtEndOf(aContent), aRootElement, &textLength,
LineBreakType::LINE_BREAK_TYPE_NATIVE, /* aIsRemovingNode = */ true); if (NS_FAILED(rv)) { return Err(rv);
} return textLength;
}
/* static */
Result<uint32_t, nsresult>
IMEContentObserver::FlatTextCache::ComputeTextLengthBeforeFirstContentOf( const nsINode& aContainer, const dom::Element* aRootElement) {
uint32_t lengthIncludingLineBreakCausedByOpenTagOfContent = 0;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::BeforeFirstContentOf(*aRootElement), // Include the line break caused by open tag of aContainer if it's an // element when we cache text length before first content of aContainer.
RawNodePosition(const_cast<nsINode*>(&aContainer), nullptr), aRootElement,
&lengthIncludingLineBreakCausedByOpenTagOfContent,
LineBreakType::LINE_BREAK_TYPE_NATIVE); if (NS_FAILED(rv)) { return Err(rv);
} return lengthIncludingLineBreakCausedByOpenTagOfContent;
}
// We can keep cache without anything if the next sibling is the first added // content. if (mContent && &aFirstContent == mContent->GetNextSibling()) { return;
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent); // We can keep the cache if added nodes are children of mContainerNode since // we cache the text length before its first child. if (mContainerNode == aFirstContent.GetParentNode()) {
AssertValidCache(aRootElement); return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot // path only for not frequent cases. Be aware, this is a hot code path // here. Therefore, expensive computation would make the DOM mutation // slower.
Clear(aCallerName); return;
}
MOZ_ASSERT(IsCachingToEndOfContent());
MOZ_ASSERT(mContent); if (aAddedFlatTextLength.isSome() &&
aLastContent.GetNextSibling() == mContent) { // If we cache test length before end of next sibling of the last added // content node, we can update the cached text simply.
CacheFlatTextLengthBeforeEndOfContent(
aCallerName, *mContent, mFlatTextLength + *aAddedFlatTextLength,
aRootElement); return;
} // Let's clear the cache for avoiding to do anything expensive for a hot // path only for not frequent cases. Be aware, this is a hot code path here. // Therefore, expensive computation would make the DOM mutation slower.
Clear(aCallerName);
}
void IMEContentObserver::FlatTextCache::ContentWillBeRemoved( const nsIContent& aContent, uint32_t aFlatTextLengthOfContent, const Element* aRootElement) { if (!mContainerNode) { return; // No cache.
}
// We can keep the cache without anything if the next sibling is removed. if (mContent && mContent == aContent.GetPreviousSibling()) { return;
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent); // We're caching text length before first child of mContainerNode. // Therefore, if a child of mContainerNode is being removed, we can keep the // cache. if (mContainerNode == aContent.GetParentNode()) {
AssertValidCache(aRootElement); return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot // path only for not frequent cases. Be aware, this is a hot code path // here. Therefore, expensive computation would make the DOM mutation // slower.
Clear("FlatTextCache::ContentRemoved"); return;
}
MOZ_ASSERT(IsCachingToEndOfContent()); if (&aContent == mContent) {
MOZ_ASSERT(mFlatTextLength >= aFlatTextLengthOfContent); if (NS_WARN_IF(mFlatTextLength < aFlatTextLengthOfContent)) {
Clear("FlatTextCache::ContentRemoved"); return;
} // We're caching text length before end of aContent. So, if there is a // previous sibling, we can cache text length before aContent with // subtracting the text length caused by aContent from the cached value. if (nsIContent* prevSibling = aContent.GetPreviousSibling()) {
CacheFlatTextLengthBeforeEndOfContent( "FlatTextCache::ContentRemoved", *prevSibling,
mFlatTextLength - aFlatTextLengthOfContent, aRootElement); return;
} // Otherwise, i.e., if aContent is first child of mContainerNode, we can // cache text length before first content of mContainerNode with subtracting // the text length caused by aContent from the cached value.
CacheFlatTextLengthBeforeFirstContent( "FlatTextCache::ContentRemoved", *mContainerNode,
mFlatTextLength - aFlatTextLengthOfContent, aRootElement); return;
} // Let's clear the cache for avoiding to do anything expensive for a hot // path only for not frequent cases. Be aware, this is a hot code path here. // Therefore, expensive computation would make the DOM mutation slower.
Clear("FlatTextCache::ContentRemoved");
}
// If new content nodes are after the cached range, we can just ignore the // new content nodes. if (*newFirstContentComparedWithCachedLastContent == 1u) {
aDifferences = OffsetAndLengthAdjustments{0, 0}; return std::make_pair(offset.inspect(), length.inspect());
}
// If new content nodes are in the cached range, we need to subtract the new // content length from cached content length. if (*newLastContentComparedWithCachedFirstContent == 1u) {
MOZ_RELEASE_ASSERT(length.inspect() >= newLength.inspect());
aDifferences = OffsetAndLengthAdjustments{0, newLength.inspect()}; return std::make_pair(offset.inspect(),
length.inspect() - newLength.inspect());
}
// If new content nodes are before the cached range, we need to subtract the // new content length from cached offset.
MOZ_RELEASE_ASSERT(offset.inspect() >= newLength.inspect());
aDifferences = OffsetAndLengthAdjustments{newLength.inspect(), 0}; return std::make_pair(offset.inspect() - newLength.inspect(),
length.inspect());
}
} // namespace mozilla
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.36 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.