828 lines
35 KiB
C++
828 lines
35 KiB
C++
/* -*- 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/. */
|
|
|
|
#ifndef mozilla_IMEContentObserver_h
|
|
#define mozilla_IMEContentObserver_h
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/EditorBase.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/Selection.h"
|
|
#include "mozilla/dom/Text.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsIDocShell.h" // XXX Why does only this need to be included here?
|
|
#include "nsIMutationObserver.h"
|
|
#include "nsIReflowObserver.h"
|
|
#include "nsIScrollObserver.h"
|
|
#include "nsIWidget.h"
|
|
#include "nsStubDocumentObserver.h"
|
|
#include "nsStubMutationObserver.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsWeakReference.h"
|
|
|
|
class nsIContent;
|
|
class nsINode;
|
|
class nsPresContext;
|
|
|
|
namespace mozilla {
|
|
|
|
class EventStateManager;
|
|
class TextComposition;
|
|
|
|
namespace dom {
|
|
class Selection;
|
|
} // namespace dom
|
|
|
|
// IMEContentObserver notifies widget of any text and selection changes
|
|
// in the currently focused editor
|
|
class IMEContentObserver final : public nsStubMutationObserver,
|
|
public nsIReflowObserver,
|
|
public nsIScrollObserver,
|
|
public nsSupportsWeakReference {
|
|
public:
|
|
using SelectionChangeData = widget::IMENotification::SelectionChangeData;
|
|
using TextChangeData = widget::IMENotification::TextChangeData;
|
|
using TextChangeDataBase = widget::IMENotification::TextChangeDataBase;
|
|
using IMENotificationRequests = widget::IMENotificationRequests;
|
|
using IMEMessage = widget::IMEMessage;
|
|
enum class ForRemoval : bool { No, Yes };
|
|
|
|
IMEContentObserver();
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IMEContentObserver,
|
|
nsIReflowObserver)
|
|
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
|
|
NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
|
|
NS_DECL_NSIREFLOWOBSERVER
|
|
|
|
// nsIScrollObserver
|
|
virtual void ScrollPositionChanged() override;
|
|
|
|
/**
|
|
* OnSelectionChange() is called when selection is changed in the editor.
|
|
*/
|
|
void OnSelectionChange(dom::Selection& aSelection);
|
|
|
|
MOZ_CAN_RUN_SCRIPT bool OnMouseButtonEvent(nsPresContext& aPresContext,
|
|
WidgetMouseEvent& aMouseEvent);
|
|
|
|
MOZ_CAN_RUN_SCRIPT nsresult
|
|
HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
|
|
|
|
/**
|
|
* Handle eSetSelection event if and only if aEvent changes selection offset
|
|
* or length. Doing nothing when selection range is same is important to
|
|
* honer users' intention or web app's intention because ContentEventHandler
|
|
* does not support to put range boundaries to arbitrary side of element
|
|
* boundaries. E.g., `<b>bold[]</b> normal` vs. `<b>bold</b>[] normal`.
|
|
* Note that this compares given range with selection cache which has been
|
|
* notified IME via widget. Therefore, the caller needs to guarantee that
|
|
* pending notifications should've been flushed. If you test this, you need
|
|
* to wait 2 animation frames before sending eSetSelection event.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT nsresult MaybeHandleSelectionEvent(
|
|
nsPresContext* aPresContext, WidgetSelectionEvent* aEvent);
|
|
|
|
/**
|
|
* Init() initializes the instance, i.e., retrieving necessary objects and
|
|
* starts to observe something.
|
|
* Be aware, callers of this method need to guarantee that the instance
|
|
* won't be released during calling this.
|
|
*
|
|
* @param aWidget The widget which can access native IME.
|
|
* @param aPresContext The PresContext which has aContent.
|
|
* @param aElement An editable element or nullptr if this will observe
|
|
* design mode document.
|
|
* @param aEditorBase The editor which is associated with aContent.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT void Init(nsIWidget& aWidget, nsPresContext& aPresContext,
|
|
dom::Element* aElement, EditorBase& aEditorBase);
|
|
|
|
/**
|
|
* Destroy() finalizes the instance, i.e., stops observing contents and
|
|
* clearing the members.
|
|
* Be aware, callers of this method need to guarantee that the instance
|
|
* won't be released during calling this.
|
|
*/
|
|
void Destroy();
|
|
|
|
/**
|
|
* Returns false if the instance refers some objects and observing them.
|
|
* Otherwise, true.
|
|
*/
|
|
bool Destroyed() const;
|
|
|
|
/**
|
|
* IMEContentObserver is stored by EventStateManager during observing.
|
|
* DisconnectFromEventStateManager() is called when EventStateManager stops
|
|
* storing the instance.
|
|
*/
|
|
void DisconnectFromEventStateManager();
|
|
|
|
/**
|
|
* MaybeReinitialize() tries to restart to observe the editor's root node.
|
|
* This is useful when the editor is reframed and all children are replaced
|
|
* with new node instances.
|
|
* Be aware, callers of this method need to guarantee that the instance
|
|
* won't be released during calling this.
|
|
*
|
|
* @return Returns true if the instance is managing the content.
|
|
* Otherwise, false.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT bool MaybeReinitialize(nsIWidget& aWidget,
|
|
nsPresContext& aPresContext,
|
|
dom::Element* aElement,
|
|
EditorBase& aEditorBase);
|
|
|
|
/**
|
|
* Return true if this is observing editable content and aElement has focus.
|
|
* If aElement is a text control, check if this is observing its anonymous
|
|
* subtree. Otherwise, check if this is observing the children of aElement in
|
|
* the DOM tree. If aElement is nullptr, this returns true if entire the
|
|
* document is editable, e.g., in the designMode.
|
|
*/
|
|
[[nodiscard]] bool IsObserving(const nsPresContext& aPresContext,
|
|
const dom::Element* aElement) const;
|
|
|
|
[[nodiscard]] bool IsBeingInitializedFor(const nsPresContext& aPresContext,
|
|
const dom::Element* aElement,
|
|
const EditorBase& aEditorBase) const;
|
|
bool IsObserving(const TextComposition& aTextComposition) const;
|
|
bool WasInitializedWith(const EditorBase& aEditorBase) const {
|
|
return mEditorBase == &aEditorBase;
|
|
}
|
|
bool IsEditorHandlingEventForComposition() const;
|
|
bool KeepAliveDuringDeactive() const {
|
|
return mIMENotificationRequests &&
|
|
mIMENotificationRequests->WantDuringDeactive();
|
|
}
|
|
[[nodiscard]] bool EditorIsTextEditor() const {
|
|
return mEditorBase && mEditorBase->IsTextEditor();
|
|
}
|
|
nsIWidget* GetWidget() const { return mWidget; }
|
|
void SuppressNotifyingIME();
|
|
void UnsuppressNotifyingIME();
|
|
nsPresContext* GetPresContext() const;
|
|
nsresult GetSelectionAndRoot(dom::Selection** aSelection,
|
|
dom::Element** aRootElement) const;
|
|
|
|
/**
|
|
* TryToFlushPendingNotifications() should be called when pending events
|
|
* should be flushed. This tries to run the queued IMENotificationSender.
|
|
* Doesn't do anything in child processes where flushing happens
|
|
* asynchronously unless aAllowAsync is false.
|
|
*/
|
|
void TryToFlushPendingNotifications(bool aAllowAsync);
|
|
|
|
/**
|
|
* MaybeNotifyCompositionEventHandled() posts composition event handled
|
|
* notification into the pseudo queue.
|
|
*/
|
|
void MaybeNotifyCompositionEventHandled();
|
|
|
|
/**
|
|
* Following methods are called when the editor:
|
|
* - an edit action handled.
|
|
* - before handling an edit action.
|
|
* - canceled handling an edit action after calling BeforeEditAction().
|
|
*/
|
|
void OnEditActionHandled();
|
|
void BeforeEditAction();
|
|
void CancelEditAction();
|
|
|
|
/**
|
|
* Called when text control value is changed while this is not observing
|
|
* mRootElement. This is typically there is no frame for the editor (i.e.,
|
|
* no proper anonymous <div> element for the editor yet) or the TextEditor
|
|
* has not been created (i.e., IMEStateManager has not been reinitialized
|
|
* this instance with new anonymous <div> element yet).
|
|
*/
|
|
void OnTextControlValueChangedWhileNotObservable(const nsAString& aNewValue);
|
|
|
|
dom::Element* GetObservingElement() const {
|
|
return mIsObserving ? mRootElement.get() : nullptr;
|
|
}
|
|
|
|
private:
|
|
~IMEContentObserver() = default;
|
|
|
|
enum State {
|
|
eState_NotObserving,
|
|
eState_Initializing,
|
|
eState_StoppedObserving,
|
|
eState_Observing
|
|
};
|
|
State GetState() const;
|
|
MOZ_CAN_RUN_SCRIPT bool InitWithEditor(nsPresContext& aPresContext,
|
|
dom::Element* aElement,
|
|
EditorBase& aEditorBase);
|
|
void OnIMEReceivedFocus();
|
|
void Clear();
|
|
[[nodiscard]] bool IsObservingContent(const nsPresContext& aPresContext,
|
|
const dom::Element* aElement) const;
|
|
[[nodiscard]] bool IsReflowLocked() const;
|
|
[[nodiscard]] bool IsSafeToNotifyIME() const;
|
|
[[nodiscard]] bool IsEditorComposing() const;
|
|
|
|
// Following methods are called by DocumentObserver when
|
|
// beginning to update the contents and ending updating the contents.
|
|
void BeginDocumentUpdate();
|
|
void EndDocumentUpdate();
|
|
|
|
// Following methods manages added nodes during a document change.
|
|
|
|
/**
|
|
* IsInDocumentChange() returns true while the DOM tree is being modified
|
|
* with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
|
|
* insertAdjacentHTML(). This returns false when user types something in
|
|
* the focused editor editor.
|
|
*/
|
|
bool IsInDocumentChange() const {
|
|
return mDocumentObserver && mDocumentObserver->IsUpdating();
|
|
}
|
|
|
|
[[nodiscard]] bool EditorIsHandlingEditSubAction() const;
|
|
|
|
void PostFocusSetNotification();
|
|
void MaybeNotifyIMEOfFocusSet();
|
|
void PostTextChangeNotification();
|
|
void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
|
|
void CancelNotifyingIMEOfTextChange();
|
|
void PostSelectionChangeNotification();
|
|
void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
|
|
bool aCausedBySelectionEvent,
|
|
bool aOccurredDuringComposition);
|
|
void PostPositionChangeNotification();
|
|
void MaybeNotifyIMEOfPositionChange();
|
|
void CancelNotifyingIMEOfPositionChange();
|
|
void PostCompositionEventHandledNotification();
|
|
|
|
void ContentAdded(nsINode* aContainer, nsIContent* aFirstContent,
|
|
nsIContent* aLastContent);
|
|
|
|
struct MOZ_STACK_CLASS OffsetAndLengthAdjustments {
|
|
[[nodiscard]] uint32_t AdjustedOffset(uint32_t aOffset) const {
|
|
MOZ_ASSERT_IF(mOffsetAdjustment < 0, aOffset >= mOffsetAdjustment);
|
|
return aOffset + mOffsetAdjustment;
|
|
}
|
|
[[nodiscard]] uint32_t AdjustedLength(uint32_t aLength) const {
|
|
MOZ_ASSERT_IF(mOffsetAdjustment < 0, aLength >= mLengthAdjustment);
|
|
return aLength + mLengthAdjustment;
|
|
}
|
|
[[nodiscard]] uint32_t AdjustedEndOffset(uint32_t aEndOffset) const {
|
|
MOZ_ASSERT_IF(mOffsetAdjustment + mLengthAdjustment < 0,
|
|
aEndOffset >= mOffsetAdjustment + mLengthAdjustment);
|
|
return aEndOffset + (mOffsetAdjustment + mLengthAdjustment);
|
|
}
|
|
|
|
int64_t mOffsetAdjustment = 0;
|
|
int64_t mLengthAdjustment = 0;
|
|
};
|
|
|
|
/**
|
|
* Posts a text change caused by cached added content in mAddedContentCache.
|
|
*
|
|
* @param aOffsetOfFirstContent
|
|
* Flattened text offset of mFirst. This can be
|
|
* different value from the computed value in the
|
|
* current tree. However, in the case,
|
|
* aAdjustments should have the difference. If this
|
|
* is Nothing, it's computed with the current DOM.
|
|
* @param aLengthOfContentNNodes
|
|
* Flattened text length starting from mFirst and
|
|
* ending by end of mLast. This can be different
|
|
* value from the computed value in the current
|
|
* tree. However, in the case, aAdjustments should
|
|
* have the difference. If this is Nothing, it's
|
|
* computed with the current DOM.
|
|
* @param aAdjustments When aOffsetOfFirstContent and/or
|
|
* aLengthOfContentNodes are specified different
|
|
* value(s) from the computed value(s) in the
|
|
* current DOM, these members should have non-zero
|
|
* values of the differences.
|
|
*/
|
|
void NotifyIMEOfCachedConsecutiveNewNodes(
|
|
const char* aCallerName,
|
|
const Maybe<uint32_t>& aOffsetOfFirstContent = Nothing(),
|
|
const Maybe<uint32_t>& aLengthOfContentNNodes = Nothing(),
|
|
const OffsetAndLengthAdjustments& aAdjustments =
|
|
OffsetAndLengthAdjustments{0, 0});
|
|
|
|
void ObserveEditableNode();
|
|
/**
|
|
* NotifyIMEOfBlur() notifies IME of blur.
|
|
*/
|
|
void NotifyIMEOfBlur();
|
|
/**
|
|
* UnregisterObservers() unregisters all listeners and observers.
|
|
*/
|
|
void UnregisterObservers();
|
|
void FlushMergeableNotifications();
|
|
bool NeedsTextChangeNotification() const {
|
|
return mIMENotificationRequests &&
|
|
mIMENotificationRequests->WantTextChange();
|
|
}
|
|
bool NeedsPositionChangeNotification() const {
|
|
return mIMENotificationRequests &&
|
|
mIMENotificationRequests->WantPositionChanged();
|
|
}
|
|
void ClearPendingNotifications() {
|
|
mNeedsToNotifyIMEOfFocusSet = false;
|
|
mNeedsToNotifyIMEOfTextChange = false;
|
|
mNeedsToNotifyIMEOfSelectionChange = false;
|
|
mNeedsToNotifyIMEOfPositionChange = false;
|
|
mNeedsToNotifyIMEOfCompositionEventHandled = false;
|
|
mTextChangeData.Clear();
|
|
}
|
|
bool NeedsToNotifyIMEOfSomething() const {
|
|
return mNeedsToNotifyIMEOfFocusSet || mNeedsToNotifyIMEOfTextChange ||
|
|
mNeedsToNotifyIMEOfSelectionChange ||
|
|
mNeedsToNotifyIMEOfPositionChange ||
|
|
mNeedsToNotifyIMEOfCompositionEventHandled;
|
|
}
|
|
|
|
/**
|
|
* UpdateSelectionCache() updates mSelectionData with the latest selection.
|
|
* This should be called only when IsSafeToNotifyIME() returns true.
|
|
*/
|
|
MOZ_CAN_RUN_SCRIPT bool UpdateSelectionCache(bool aRequireFlush = true);
|
|
|
|
nsCOMPtr<nsIWidget> mWidget;
|
|
// mFocusedWidget has the editor observed by the instance. E.g., if the
|
|
// focused editor is in XUL panel, this should be the widget of the panel.
|
|
// On the other hand, mWidget is its parent which handles IME.
|
|
nsCOMPtr<nsIWidget> mFocusedWidget;
|
|
RefPtr<dom::Selection> mSelection;
|
|
RefPtr<dom::Element> mRootElement;
|
|
nsCOMPtr<nsINode> mEditableNode;
|
|
nsCOMPtr<nsIDocShell> mDocShell;
|
|
RefPtr<EditorBase> mEditorBase;
|
|
|
|
/**
|
|
* Helper classes to notify IME.
|
|
*/
|
|
|
|
class AChangeEvent : public Runnable {
|
|
protected:
|
|
enum ChangeEventType {
|
|
eChangeEventType_Focus,
|
|
eChangeEventType_Selection,
|
|
eChangeEventType_Text,
|
|
eChangeEventType_Position,
|
|
eChangeEventType_CompositionEventHandled
|
|
};
|
|
|
|
explicit AChangeEvent(const char* aName,
|
|
IMEContentObserver* aIMEContentObserver)
|
|
: Runnable(aName),
|
|
mIMEContentObserver(do_GetWeakReference(
|
|
static_cast<nsIReflowObserver*>(aIMEContentObserver))) {
|
|
MOZ_ASSERT(aIMEContentObserver);
|
|
}
|
|
|
|
already_AddRefed<IMEContentObserver> GetObserver() const {
|
|
nsCOMPtr<nsIReflowObserver> observer =
|
|
do_QueryReferent(mIMEContentObserver);
|
|
return observer.forget().downcast<IMEContentObserver>();
|
|
}
|
|
|
|
nsWeakPtr mIMEContentObserver;
|
|
|
|
/**
|
|
* CanNotifyIME() checks if mIMEContentObserver can and should notify IME.
|
|
*/
|
|
bool CanNotifyIME(ChangeEventType aChangeEventType) const;
|
|
|
|
/**
|
|
* IsSafeToNotifyIME() checks if it's safe to noitify IME.
|
|
*/
|
|
bool IsSafeToNotifyIME(ChangeEventType aChangeEventType) const;
|
|
};
|
|
|
|
class IMENotificationSender : public AChangeEvent {
|
|
public:
|
|
explicit IMENotificationSender(IMEContentObserver* aIMEContentObserver)
|
|
: AChangeEvent("IMENotificationSender", aIMEContentObserver),
|
|
mIsRunning(false) {}
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
|
|
|
|
void Dispatch(nsIDocShell* aDocShell);
|
|
|
|
private:
|
|
MOZ_CAN_RUN_SCRIPT void SendFocusSet();
|
|
MOZ_CAN_RUN_SCRIPT void SendSelectionChange();
|
|
void SendTextChange();
|
|
void SendPositionChange();
|
|
void SendCompositionEventHandled();
|
|
|
|
bool mIsRunning;
|
|
};
|
|
|
|
// mQueuedSender is, it was put into the event queue but not run yet.
|
|
RefPtr<IMENotificationSender> mQueuedSender;
|
|
|
|
/**
|
|
* IMEContentObserver is a mutation observer of mRootContent. However,
|
|
* it needs to know the beginning of content changes and end of it too for
|
|
* reducing redundant computation of text offset with ContentEventHandler.
|
|
* Therefore, it needs helper class to listen only them since if
|
|
* both mutations were observed by IMEContentObserver directly, each
|
|
* methods need to check if the changing node is in mRootContent but it's
|
|
* too expensive.
|
|
*/
|
|
class DocumentObserver final : public nsStubDocumentObserver {
|
|
public:
|
|
DocumentObserver() = delete;
|
|
explicit DocumentObserver(IMEContentObserver& aIMEContentObserver)
|
|
: mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) {
|
|
SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate |
|
|
nsIMutationObserver::kEndUpdate);
|
|
}
|
|
|
|
NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver)
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
|
|
NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
|
|
|
|
void Observe(dom::Document*);
|
|
void StopObserving();
|
|
void Destroy();
|
|
|
|
bool Destroyed() const { return !mIMEContentObserver; }
|
|
bool IsObserving() const { return mDocument != nullptr; }
|
|
bool IsUpdating() const { return mDocumentUpdating != 0; }
|
|
|
|
private:
|
|
virtual ~DocumentObserver() { Destroy(); }
|
|
|
|
RefPtr<IMEContentObserver> mIMEContentObserver;
|
|
RefPtr<dom::Document> mDocument;
|
|
uint32_t mDocumentUpdating;
|
|
};
|
|
RefPtr<DocumentObserver> mDocumentObserver;
|
|
|
|
/**
|
|
* FlatTextCache stores length of flattened text starting from start of
|
|
* the observing node (typically editing host or the anonymous <div> of
|
|
* TextEditor) to:
|
|
* - end of mContent if it's set (IsCachingToEndOfContent() returns true)
|
|
* - before first content of mContainerNode if mContent is not set
|
|
* (IsCachingToStartOfContainer() returns true). In this case, the text
|
|
* length includes a line break length which is caused by the open tag of
|
|
* mContainerNode if and only if it's an element node and the open tag causes
|
|
* a line break.
|
|
*/
|
|
struct FlatTextCache {
|
|
public:
|
|
explicit FlatTextCache(const char* aInstanceName)
|
|
: mInstanceName(aInstanceName) {}
|
|
|
|
void Clear(const char* aCallerName);
|
|
|
|
[[nodiscard]] bool HasCache() const { return !!mContainerNode; }
|
|
|
|
/**
|
|
* Return true if mFlatTextLength caches flattened text length starting from
|
|
* start of the observing node to the end of mContent.
|
|
*/
|
|
[[nodiscard]] bool IsCachingToEndOfContent() const {
|
|
return mContainerNode && mContent;
|
|
}
|
|
|
|
/**
|
|
* Return true if mFlatTextLength caches flattened text length starting from
|
|
* start of the observing node to the start of mContainerNode. Note that if
|
|
* mContainerNode is an element and whose open tag causes a line break,
|
|
* mFlatTextLength includes the line break length too.
|
|
*/
|
|
[[nodiscard]] bool IsCachingToStartOfContainer() const {
|
|
return mContainerNode && !mContent;
|
|
}
|
|
|
|
/**
|
|
* Compute flattened text length starting from first content of aRootElement
|
|
* and ending at end of aContent.
|
|
*
|
|
* @param aContent This will be set to mContent which points the
|
|
* last child content node which participates in
|
|
* the computed mFlatTextLength.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* (This is required to suppress
|
|
* ContentEventHandler to generate a line break
|
|
* caused by open tag of the editable root element
|
|
* due to not editable. Therefore, we need to call
|
|
* ContentEventHandler methods with this.)
|
|
*/
|
|
[[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeEndOfContent(
|
|
const char* aCallerName, const nsIContent& aContent,
|
|
const dom::Element* aRootElement);
|
|
|
|
void CacheFlatTextLengthBeforeEndOfContent(
|
|
const char* aCallerName, const nsIContent& aContent,
|
|
uint32_t aFlatTextLength, const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Compute flattened text length starting from first content of aRootElement
|
|
* and ending at start of the first content of aContainer.
|
|
*
|
|
* @param aContainer This will be set to mContainer and mContent will
|
|
* be set to nullptr.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* (This is required to suppress
|
|
* ContentEventHandler to generate a line break
|
|
* caused by open tag of the editable root element
|
|
* due to not editable. Therefore, we need to call
|
|
* ContentEventHandler methods with this.)
|
|
*/
|
|
[[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeFirstContent(
|
|
const char* aCallerName, const nsINode& aContainer,
|
|
const dom::Element* aRootElement);
|
|
|
|
void CacheFlatTextLengthBeforeFirstContent(
|
|
const char* aCallerName, const nsINode& aContainer,
|
|
uint32_t aFlatTextLength, const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Return flattened text length of aContent. I.e., the length includes a
|
|
* line break caused by the open tag of aContent if it's an element node.
|
|
*
|
|
* @param aRemovingContent The content node which is being removed.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* For avoiding to generate a redundant line break
|
|
* at open tag of this element, this is required
|
|
* to call methods of ContentEventHandler.
|
|
* @param aForRemoval Whether aContent is about to be removed.
|
|
*/
|
|
[[nodiscard]] static Result<uint32_t, nsresult> ComputeTextLengthOfContent(
|
|
const nsIContent& aContent, const dom::Element* aRootElement,
|
|
ForRemoval = ForRemoval::No);
|
|
|
|
/**
|
|
* Return flattened text length of starting from first content of
|
|
* aRootElement and ending at before aContent (if ContentEventHandler
|
|
* generates a line break at open tag of aContent, the result does not
|
|
* contain the line break length).
|
|
*
|
|
* @param aContent The content node which is immediately after a
|
|
* content which you want to compute the flattened
|
|
* text length before end of it.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* For avoiding to generate a redundant line break
|
|
* at open tag of this element, this is required
|
|
* to call methods of ContentEventHandler.
|
|
*/
|
|
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
ComputeTextLengthBeforeContent(const nsIContent& aContent,
|
|
const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Return flattened text length starting from first content of aRootElement
|
|
* and ending at start of the first content of aContainer. This means that
|
|
* if ContentEventHandler generates a line break at the open tag of
|
|
* aContainer, the result includes the line break length.
|
|
* NOTE: The difference from ComputeTextLengthBeforeContent() is, result of
|
|
* this method includes a line break caused by the open tag of aContainer
|
|
* if and only if it's an element node and ContentEventHandler generates
|
|
* a line break for its open tag.
|
|
*
|
|
* @param aContainer The container node which you want to compute the
|
|
* flattened text length before the first content
|
|
* of.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* For avoiding to generate a redundant line break
|
|
* at open tag of this element, this is required
|
|
* to call methods of ContentEventHandler.
|
|
*/
|
|
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
ComputeTextLengthBeforeFirstContentOf(const nsINode& aContainer,
|
|
const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Return flattened text length of starting from start of aStartContent and
|
|
* ending at end of aEndContent. If ContentEventHandler generates a line
|
|
* break at open tag of aStartContent, the result includes the line break
|
|
* length.
|
|
*
|
|
* @param aStartContent The first content node of consecutive nodes
|
|
* which you want to compute flattened text length
|
|
* starting from.
|
|
* @param aEndContent The last content node of consecutive nodes
|
|
* which you want to compute flattened text length
|
|
* ending at.
|
|
* @param aRootElement The root element of the editor, i.e., editing
|
|
* host or the anonymous <div> in a text control.
|
|
* For avoiding to generate a redundant line break
|
|
* at open tag of this element, this is required
|
|
* to call methods of ContentEventHandler.
|
|
*/
|
|
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
ComputeTextLengthStartOfContentToEndOfContent(
|
|
const nsIContent& aStartContent, const nsIContent& aEndContent,
|
|
const dom::Element* aRootElement);
|
|
|
|
[[nodiscard]] uint32_t GetFlatTextLength() const { return mFlatTextLength; }
|
|
|
|
/**
|
|
* Return text length if it's exactly cached or can compute it quickly from
|
|
* the cached data. aContent must not be new node which is inserted before
|
|
* mContent because the cached text length does not include the text length
|
|
* of aContent in such case.
|
|
*/
|
|
[[nodiscard]] Maybe<uint32_t> GetFlatTextLengthBeforeContent(
|
|
const nsIContent& aContent, const dom::Element* aRootElement,
|
|
ForRemoval = ForRemoval::No) const;
|
|
|
|
/**
|
|
* Return text length before aFirstContent if it's exactly cached or can
|
|
* compute it quickly from the caching data. This is called when the nodes
|
|
* between aFirstContent and aLastContent are inserted into the tree.
|
|
*/
|
|
[[nodiscard]] Maybe<uint32_t> GetFlatTextOffsetOnInsertion(
|
|
const nsIContent& aFirstContent, const nsIContent& aLastContent,
|
|
const dom::Element* aRootElement) const;
|
|
|
|
/**
|
|
* This works only in the debug build and
|
|
* test.ime_content_observer.assert_valid_cache pref is enabled. This
|
|
* checks with expensive computation, therefore, the pref is enabled only
|
|
* when running automated tests for editors.
|
|
*/
|
|
void AssertValidCache(const dom::Element* aRootElement) const;
|
|
|
|
/**
|
|
* Called when content nodes from aFirstContent to aLastContent are added.
|
|
* aAddedFlatTextLength may be flattened text length from start of
|
|
* aFirstContent to end of aLastContent if it's computed by the caller.
|
|
* Note that aFirstContent and aLastContent can be in different container
|
|
* nodes, but this method is currently called with (maybe indirect) siblings
|
|
* in the same container.
|
|
*/
|
|
void ContentAdded(const char* aCallerName, const nsIContent& aFirstContent,
|
|
const nsIContent& aLastContent,
|
|
const Maybe<uint32_t>& aAddedFlatTextLength,
|
|
const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Called when aContent will be removed. aFlatTextLengthOfContent is
|
|
* flattened text length of aContent.
|
|
*/
|
|
void ContentWillBeRemoved(const nsIContent& aContent,
|
|
uint32_t aFlatTextLengthOfContent,
|
|
const dom::Element* aRootElement);
|
|
|
|
public:
|
|
// mContainerNode is parent node of mContent when it's cached.
|
|
nsCOMPtr<nsINode> mContainerNode;
|
|
// mContent points to the last child which participates in the current
|
|
// mFlatTextLength. If this is nullptr, mFlatTextLength means that it
|
|
// length before the first content of mContainerNode, i.e., including the
|
|
// line break of that caused by the open tag of mContainerNode.
|
|
nsCOMPtr<nsIContent> mContent;
|
|
|
|
private:
|
|
// Length of flat text generated from contents between the start of the
|
|
// observing node (typically editing host or the anonymous <div> of
|
|
// TextEditor) and the end of mContent.
|
|
uint32_t mFlatTextLength = 0;
|
|
MOZ_DEFINE_DBG(FlatTextCache, mContainerNode, mContent, mFlatTextLength);
|
|
|
|
const char* mInstanceName;
|
|
};
|
|
|
|
friend std::ostream& operator<<(std::ostream& aStream,
|
|
const FlatTextCache& aCache);
|
|
|
|
// mEndOfAddedTextCache caches text length from the start of the observing
|
|
// node to the end of the last added content only while an edit action is
|
|
// being handled by the editor and no other mutation (e.g., removing node)
|
|
// occur.
|
|
FlatTextCache mEndOfAddedTextCache = FlatTextCache("mEndOfAddedTextCache");
|
|
// mStartOfRemovingTextRangeCache caches text length from the start of the
|
|
// observing node to the start of the last removed content only while an edit
|
|
// action is being handled by the editor and no other mutation (e.g., adding
|
|
// node) occur. In other words, this caches text length before end of
|
|
// mContent or before first child of mContainerNode.
|
|
FlatTextCache mStartOfRemovingTextRangeCache =
|
|
FlatTextCache("mStartOfRemovingTextRangeCache");
|
|
|
|
/**
|
|
* Caches the DOM node ranges with storing the first node and the last node.
|
|
* This is designed for mAddedContentCache. See comment at declaration of it
|
|
* for the detail.
|
|
*/
|
|
struct AddedContentCache {
|
|
/**
|
|
* Clear the range. Callers should call this with __FUNCTION__ which will be
|
|
* used to log which caller did it.
|
|
*/
|
|
void Clear(const char* aCallerName);
|
|
|
|
[[nodiscard]] bool HasCache() const { return mFirst && mLast; }
|
|
|
|
/**
|
|
* Return true if aFirstContent and aLastContent can be merged into the
|
|
* cached range. This should be called only when the instance caches
|
|
* something.
|
|
*/
|
|
[[nodiscard]] bool CanMergeWith(const nsIContent& aFirstContent,
|
|
const nsIContent& aLastContent,
|
|
const dom::Element* aRootElement) const;
|
|
|
|
/**
|
|
* Return true if aContent is in the cached range. aContent can be not
|
|
* a child of the common container of the caching range.
|
|
*/
|
|
[[nodiscard]] bool IsInRange(const nsIContent& aContent,
|
|
const dom::Element* aRootElement) const;
|
|
|
|
/**
|
|
* Try to cache the range represented by aFirstContent and aLastContent.
|
|
* If there is a cache, this will extend the caching range to contain
|
|
* the new range.
|
|
*
|
|
* @return true if cached, otherwise, false.
|
|
*/
|
|
bool TryToCache(const nsIContent& aFirstContent,
|
|
const nsIContent& aLastContent,
|
|
const dom::Element* aRootElement);
|
|
|
|
/**
|
|
* Compute offset and length of the cached range before the nodes between
|
|
* aNewFirstContent and aNewLastContent are inserted.
|
|
*
|
|
* @return The first one is offset, the other is length.
|
|
*/
|
|
[[nodiscard]] Result<std::pair<uint32_t, uint32_t>, nsresult>
|
|
ComputeFlatTextRangeBeforeInsertingNewContent(
|
|
const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent,
|
|
const dom::Element* aRootElement,
|
|
OffsetAndLengthAdjustments& aDifferences) const;
|
|
|
|
MOZ_DEFINE_DBG(AddedContentCache, mFirst, mLast);
|
|
|
|
nsCOMPtr<nsIContent> mFirst;
|
|
nsCOMPtr<nsIContent> mLast;
|
|
};
|
|
|
|
// Caches the first node and the last node of new inserted nodes while editor
|
|
// handles an editing command/operation. Therefore, the range is always in
|
|
// the same container node. So, the range means that the direct siblings
|
|
// between the first node and the last node are the inserted nodes, but not
|
|
// yet post a text change notification.
|
|
// FYI: This is cleared when editor ends handling current edit
|
|
// operation/command. Therefore, the strong pointers in this member don't
|
|
// need to be added to the cycle collection.
|
|
AddedContentCache mAddedContentCache;
|
|
|
|
TextChangeData mTextChangeData;
|
|
|
|
// mSelectionData is the last selection data which was notified. The
|
|
// selection information is modified by UpdateSelectionCache(). The reason
|
|
// of the selection change is modified by MaybeNotifyIMEOfSelectionChange().
|
|
SelectionChangeData mSelectionData;
|
|
|
|
EventStateManager* mESM = nullptr;
|
|
|
|
const IMENotificationRequests* mIMENotificationRequests = nullptr;
|
|
int64_t mPreCharacterDataChangeLength = -1;
|
|
uint32_t mSuppressNotifications = 0;
|
|
|
|
// If the observing editor is a text control's one, this is set to the value
|
|
// length.
|
|
uint32_t mTextControlValueLength = 0;
|
|
|
|
// mSendingNotification is a notification which is now sending from
|
|
// IMENotificationSender. When the value is NOTIFY_IME_OF_NOTHING, it's
|
|
// not sending any notification.
|
|
IMEMessage mSendingNotification = widget::NOTIFY_IME_OF_NOTHING;
|
|
|
|
bool mIsObserving = false;
|
|
bool mIsTextControl = false;
|
|
bool mIMEHasFocus = false;
|
|
bool mNeedsToNotifyIMEOfFocusSet = false;
|
|
bool mNeedsToNotifyIMEOfTextChange = false;
|
|
bool mNeedsToNotifyIMEOfSelectionChange = false;
|
|
bool mNeedsToNotifyIMEOfPositionChange = false;
|
|
bool mNeedsToNotifyIMEOfCompositionEventHandled = false;
|
|
// mIsHandlingQueryContentEvent is true when IMEContentObserver is handling
|
|
// WidgetQueryContentEvent with ContentEventHandler.
|
|
bool mIsHandlingQueryContentEvent = false;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_IMEContentObserver_h
|