tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdom/base/nsContentUtils.cpp | 15+++++++++++++++
Meditor/libeditor/TextEditor.cpp | 119++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
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,