commit 6ba307dd8f601fe1b58f2fd12a04d73eb720c663
parent dc0c4c03e2a692e3d7898e79b7370e43b93a7a60
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Wed, 12 Nov 2025 22:52:08 +0000
Bug 1824143 - part 1: Log `TextEditor` creation/destruction/focus/blur and when `input` and `beforeinput` events are fired r=m_kato
It's important to verify that when `TextEditor` is destroyed temporarily
if the text control frame is reframed by the web app during text input.
Therefore, this patch adds the logger for the timings of `TextEditor`
creation, destruction, focus and blur **and** the common `InputEvent`
dispatcher.
Differential Revision: https://phabricator.services.mozilla.com/D271558
Diffstat:
2 files changed, 115 insertions(+), 19 deletions(-)
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
@@ -486,6 +486,9 @@ bool nsContentUtils::sMayHaveFormRadioStateChangeListeners = false;
mozilla::LazyLogModule nsContentUtils::gResistFingerprintingLog(
"nsResistFingerprinting");
mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
+// Log when "input" and "beforeinput" events are dispatched or enqueued with
+// InputEvent:3,sync.
+mozilla::LazyLogModule gInputEventLog("InputEvent");
int32_t nsContentUtils::sInnerOrOuterWindowCount = 0;
uint32_t nsContentUtils::sInnerOrOuterWindowSerialCounter = 0;
@@ -5534,6 +5537,11 @@ nsresult nsContentUtils::DispatchInputEvent(
widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
widgetEvent.mFlags.mCancelable = false;
widgetEvent.mFlags.mComposed = true;
+ MOZ_LOG(gInputEventLog, LogLevel::Info,
+ ("Dispatching %s, safe?=%s, aEditorBase=%p, aEventTargetElement=%s",
+ ToChar(widgetEvent.mMessage),
+ YesOrNo(nsContentUtils::IsSafeToRunScript()), aEditorBase,
+ ToString(RefPtr{aEventTargetElement}).c_str()));
return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
widgetEvent, aEventStatus);
}
@@ -5643,6 +5651,13 @@ nsresult nsContentUtils::DispatchInputEvent(
"Cancelable beforeinput event dispatcher should run when it's safe");
inputEvent.mFlags.mCancelable = false;
}
+ MOZ_LOG(gInputEventLog, LogLevel::Info,
+ ("Dispatching %s, safe?=%s, inputType=%s, aEditorBase=%p, "
+ "aEventTargetElement=%s",
+ ToChar(inputEvent.mMessage),
+ YesOrNo(nsContentUtils::IsSafeToRunScript()),
+ ToString(inputEvent.mInputType).c_str(), aEditorBase,
+ ToString(RefPtr{aEventTargetElement}).c_str()));
return AsyncEventDispatcher::RunDOMEventWhenSafe(*aEventTargetElement,
inputEvent, aEventStatus);
}
diff --git a/editor/libeditor/TextEditor.cpp b/editor/libeditor/TextEditor.cpp
@@ -20,6 +20,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/IMEStateManager.h"
+#include "mozilla/Logging.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/mozalloc.h"
#include "mozilla/Preferences.h"
@@ -70,6 +71,22 @@ class nsISupports;
namespace mozilla {
+// This logs the important things for the lifecycle of the TextEditor.
+LazyLogModule gTextEditorLog("TextEditor");
+
+static void LogOrWarn(const TextEditor* aTextEditor, LazyLogModule& aLog,
+ LogLevel aLogLevel, const char* aStr) {
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(aLog, aLogLevel)) {
+ MOZ_LOG(aLog, aLogLevel, ("%p: %s", aTextEditor, aStr));
+ } else {
+ NS_WARNING(aStr);
+ }
+#else
+ MOZ_LOG(aLog, aLogLevel, ("%p: %s", aTextEditor, aStr));
+#endif
+}
+
using namespace dom;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
@@ -85,12 +102,16 @@ TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text) {
static_assert(
sizeof(TextEditor) <= 512,
"TextEditor instance should be allocatable in the quantum class bins");
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: New instance is created", this));
}
TextEditor::~TextEditor() {
// Remove event listeners. Note that if we had an HTML editor,
// it installed its own instead of these
RemoveEventListeners();
+
+ MOZ_LOG(gTextEditorLog, LogLevel::Info, ("%p: Deleted", this));
}
NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
@@ -153,18 +174,28 @@ nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement,
MOZ_ASSERT(!mInitSucceeded,
"TextEditor::Init() called again without calling PreDestroy()?");
MOZ_ASSERT(!(aFlags & nsIEditor::eEditorPasswordMask) == !aPasswordMaskData);
+
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: Init(aDocument=%p, aAnonymousDivElement=%s, "
+ "aSelectionController=%p, aPasswordMaskData=%p)",
+ this, &aDocument, ToString(RefPtr{&aAnonymousDivElement}).c_str(),
+ &aSelectionController, aPasswordMaskData.get()));
+
mPasswordMaskData = std::move(aPasswordMaskData);
// Init the base editor
nsresult rv = InitInternal(aDocument, &aAnonymousDivElement,
aSelectionController, aFlags);
if (NS_FAILED(rv)) {
- NS_WARNING("EditorBase::InitInternal() failed");
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::InitInternal() failed");
return rv;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
- if (NS_WARN_IF(!editActionData.CanHandle())) {
+ if (MOZ_UNLIKELY(!editActionData.CanHandle())) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "AutoEditActionDataSetter::CanHandle() failed");
return NS_ERROR_FAILURE;
}
@@ -176,7 +207,8 @@ nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement,
rv = InitEditorContentAndSelection();
if (NS_FAILED(rv)) {
- NS_WARNING("TextEditor::InitEditorContentAndSelection() failed");
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "TextEditor::InitEditorContentAndSelection() failed");
// XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
// is a public method?
mInitSucceeded = false;
@@ -200,7 +232,8 @@ nsresult TextEditor::InitEditorContentAndSelection() {
if (!SelectionRef().RangeCount()) {
nsresult rv = CollapseSelectionToEndOfTextNode();
if (NS_FAILED(rv)) {
- NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed");
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::CollapseSelectionToEndOfTextNode() failed");
return rv;
}
}
@@ -208,8 +241,8 @@ nsresult TextEditor::InitEditorContentAndSelection() {
if (!IsSingleLineEditor()) {
nsresult rv = EnsurePaddingBRElementInMultilineEditor();
if (NS_FAILED(rv)) {
- NS_WARNING(
- "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
return rv;
}
}
@@ -218,8 +251,14 @@ nsresult TextEditor::InitEditorContentAndSelection() {
}
nsresult TextEditor::PostCreate() {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: PostCreate(), mDidPostCreate=%s", this,
+ TrueOrFalse(mDidPostCreate)));
+
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
- if (NS_WARN_IF(!editActionData.CanHandle())) {
+ if (MOZ_UNLIKELY(!editActionData.CanHandle())) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "AutoEditActionDataSetter::CanHandle() failed");
return NS_ERROR_NOT_INITIALIZED;
}
@@ -233,12 +272,21 @@ nsresult TextEditor::PostCreate() {
"TextEditor::SetUnmaskRangeAndNotify() failed to "
"restore unmasked range, but ignored");
}
- NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
- "EditorBase::PostCreateInternal() failed");
- return rv;
+
+ if (NS_FAILED(rv)) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::PostCreateInternal() failed");
+ return rv;
+ }
+
+ return NS_OK;
}
UniquePtr<PasswordMaskData> TextEditor::PreDestroy() {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: PreDestroy() mDidPreDestroy=%s", this,
+ TrueOrFalse(mDidPreDestroy)));
+
if (mDidPreDestroy) {
return nullptr;
}
@@ -699,12 +747,19 @@ nsresult TextEditor::SelectEntireDocument() {
EventTarget* TextEditor::GetDOMEventTarget() const { return mEventTarget; }
void TextEditor::ReinitializeSelection(Element& aElement) {
- if (NS_WARN_IF(Destroyed())) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: ReinitializeSelection(aElement=%s)", this,
+ ToString(RefPtr{&aElement}).c_str()));
+
+ if (MOZ_UNLIKELY(Destroyed())) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error, "Destroyed() failed");
return;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
- if (NS_WARN_IF(!editActionData.CanHandle())) {
+ if (MOZ_UNLIKELY(!editActionData.CanHandle())) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "AutoEditActionDataSetter::CanHandle() failed");
return;
}
@@ -719,32 +774,46 @@ void TextEditor::ReinitializeSelection(Element& aElement) {
}
nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: OnFocus(aOriginalEventTargetNode=%s)", this,
+ ToString(RefPtr{&aOriginalEventTargetNode}).c_str()));
+
RefPtr<PresShell> presShell = GetPresShell();
- if (NS_WARN_IF(!presShell)) {
+ if (MOZ_UNLIKELY(!presShell)) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error, "!presShell");
return NS_ERROR_FAILURE;
}
// Let's update the layout information right now because there are some
// pending notifications and flushing them may cause destroying the editor.
presShell->FlushPendingNotifications(FlushType::Layout);
if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Debug,
+ ("%p: CanKeepHandlingFocusEvent() returned false", this));
return NS_OK;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
- if (NS_WARN_IF(!editActionData.CanHandle())) {
+ if (MOZ_UNLIKELY(!editActionData.CanHandle())) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "AutoEditActionDataSetter::CanHandle() failed");
return NS_ERROR_FAILURE;
}
// Spell check a textarea the first time that it is focused.
nsresult rv = FlushPendingSpellCheck();
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
- NS_WARNING("EditorBase::FlushPendingSpellCheck() failed");
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::FlushPendingSpellCheck() failed");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::FlushPendingSpellCheck() failed, but ignored");
if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Debug,
+ ("%p: CanKeepHandlingFocusEvent() returned false after "
+ "FlushPendingSpellCheck()",
+ this));
return NS_OK;
}
@@ -752,18 +821,30 @@ nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
}
nsresult TextEditor::OnBlur(const EventTarget* aEventTarget) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: OnBlur(aEventTarget=%s)", this,
+ ToString(RefPtr{aEventTarget}).c_str()));
+
// check if something else is focused. If another element is focused, then
// we should not change the selection. If another element already has focus,
// we should not maintain the selection because we may not have the rights
// doing it.
- if (nsFocusManager::GetFocusedElementStatic()) {
+ if ([[maybe_unused]] Element* const focusedElement =
+ nsFocusManager::GetFocusedElementStatic()) {
+ MOZ_LOG(gTextEditorLog, LogLevel::Info,
+ ("%p: OnBlur() is ignored because another element already has "
+ "focus (%s)",
+ this, ToString(RefPtr{focusedElement}).c_str()));
return NS_OK;
}
nsresult rv = FinalizeSelection();
- NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
- "EditorBase::FinalizeSelection() failed");
- return rv;
+ if (NS_FAILED(rv)) {
+ LogOrWarn(this, gTextEditorLog, LogLevel::Error,
+ "EditorBase::FinalizeSelection() failed");
+ return rv;
+ }
+ return NS_OK;
}
nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement,