Bug 1612477 - part 1: Make ComposerCommandsUpdater not inherit nsIDocumentStateListener r=m_kato

This patch makes `EditorBase::NotifyDocumentListeners()` notify
`ComposerCommandsUpdater` via `HTMLEditor::mComposerCommandsUpdater` directly.
Therefore, `ComposerCommandsUpdater` can stop inheriting
`nsIDocumentStateListener`.

Note that this patch also makes `ComposerCommandsUpdater::UpdateCommandGroup()`
not take `nsAString` as its parameter because inlinning the
`nsIDocumentStateListener` requires `ComposerCommandsUpdater.h` to include
`nsAString.h`, but it's redundant and `UpdateCommandGroup()` just compares
it with literal strings.  Therefore, using `enum class` for specifying command
group is faster.

Differential Revision: https://phabricator.services.mozilla.com/D61357

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2020-02-03 07:35:44 +00:00
parent b36c4e44b6
commit cc2af30635
5 changed files with 163 additions and 194 deletions

View File

@ -9,7 +9,6 @@
#include "mozilla/mozalloc.h" // for operator new
#include "mozilla/TransactionManager.h" // for TransactionManager
#include "mozilla/dom/Selection.h"
#include "nsAString.h"
#include "nsCommandManager.h" // for nsCommandManager
#include "nsComponentManagerUtils.h" // for do_CreateInstance
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
@ -40,11 +39,10 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(ComposerCommandsUpdater)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ComposerCommandsUpdater)
NS_INTERFACE_MAP_BEGIN(ComposerCommandsUpdater)
NS_INTERFACE_MAP_ENTRY(nsIDocumentStateListener)
NS_INTERFACE_MAP_ENTRY(nsITransactionListener)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsINamed)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentStateListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionListener)
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ComposerCommandsUpdater)
NS_INTERFACE_MAP_END
@ -55,58 +53,22 @@ NS_IMPL_CYCLE_COLLECTION(ComposerCommandsUpdater, mUpdateTimer, mDOMWindow,
# pragma mark -
#endif
NS_IMETHODIMP
ComposerCommandsUpdater::NotifyDocumentCreated() {
// Trigger an nsIObserve notification that the document has been created
UpdateOneCommand("obs_documentCreated");
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::NotifyDocumentWillBeDestroyed() {
// cancel any outstanding update timer
if (mUpdateTimer) {
mUpdateTimer->Cancel();
mUpdateTimer = nullptr;
}
// We can't call this right now; it is too late in some cases and the window
// is already partially destructed (e.g. JS objects may be gone).
#if 0
// Trigger an nsIObserve notification that the document will be destroyed
UpdateOneCommand("obs_documentWillBeDestroyed");
#endif
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::NotifyDocumentStateChanged(bool aNowDirty) {
// update document modified. We should have some other notifications for this
// too.
return UpdateDirtyState(aNowDirty);
}
#if 0
# pragma mark -
#endif
NS_IMETHODIMP
ComposerCommandsUpdater::WillDo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillDo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidDo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aDoResult) {
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHODIMP ComposerCommandsUpdater::DidDo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aDoResult) {
// only need to update if the status of the Undo menu item changes.
size_t undoCount = aManager->AsTransactionManager()->NumberOfUndoItems();
if (undoCount == 1) {
if (mFirstDoOfFirstUndo) {
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
UpdateCommandGroup(CommandGroup::Undo);
}
mFirstDoOfFirstUndo = false;
}
@ -114,82 +76,73 @@ ComposerCommandsUpdater::DidDo(nsITransactionManager* aManager,
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::WillUndo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillUndo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidUndo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aUndoResult) {
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHODIMP ComposerCommandsUpdater::DidUndo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aUndoResult) {
size_t undoCount = aManager->AsTransactionManager()->NumberOfUndoItems();
if (!undoCount) {
mFirstDoOfFirstUndo = true; // reset the state for the next do
}
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
UpdateCommandGroup(CommandGroup::Undo);
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::WillRedo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillRedo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidRedo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aRedoResult) {
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHODIMP ComposerCommandsUpdater::DidRedo(nsITransactionManager* aManager,
nsITransaction* aTransaction,
nsresult aRedoResult) {
UpdateCommandGroup(CommandGroup::Undo);
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::WillBeginBatch(nsITransactionManager* aManager,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillBeginBatch(
nsITransactionManager* aManager, bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidBeginBatch(nsITransactionManager* aManager,
nsresult aResult) {
NS_IMETHODIMP ComposerCommandsUpdater::DidBeginBatch(
nsITransactionManager* aManager, nsresult aResult) {
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::WillEndBatch(nsITransactionManager* aManager,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillEndBatch(
nsITransactionManager* aManager, bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidEndBatch(nsITransactionManager* aManager,
nsresult aResult) {
NS_IMETHODIMP ComposerCommandsUpdater::DidEndBatch(
nsITransactionManager* aManager, nsresult aResult) {
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::WillMerge(nsITransactionManager* aManager,
nsITransaction* aTopTransaction,
nsITransaction* aTransactionToMerge,
bool* aInterrupt) {
NS_IMETHODIMP ComposerCommandsUpdater::WillMerge(
nsITransactionManager* aManager, nsITransaction* aTopTransaction,
nsITransaction* aTransactionToMerge, bool* aInterrupt) {
*aInterrupt = false;
return NS_OK;
}
NS_IMETHODIMP
ComposerCommandsUpdater::DidMerge(nsITransactionManager* aManager,
nsITransaction* aTopTransaction,
nsITransaction* aTransactionToMerge,
bool aDidMerge, nsresult aMergeResult) {
NS_IMETHODIMP ComposerCommandsUpdater::DidMerge(
nsITransactionManager* aManager, nsITransaction* aTopTransaction,
nsITransaction* aTransactionToMerge, bool aDidMerge,
nsresult aMergeResult) {
return NS_OK;
}
@ -205,90 +158,66 @@ void ComposerCommandsUpdater::Init(nsPIDOMWindowOuter& aDOMWindow) {
nsresult ComposerCommandsUpdater::PrimeUpdateTimer() {
if (!mUpdateTimer) {
mUpdateTimer = NS_NewTimer();
;
NS_ENSURE_TRUE(mUpdateTimer, NS_ERROR_OUT_OF_MEMORY);
}
const uint32_t kUpdateTimerDelay = 150;
return mUpdateTimer->InitWithCallback(static_cast<nsITimerCallback*>(this),
kUpdateTimerDelay,
nsITimer::TYPE_ONE_SHOT);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void ComposerCommandsUpdater::TimerCallback() {
// if the selection state has changed, update stuff
bool isCollapsed = SelectionIsCollapsed();
if (static_cast<int8_t>(isCollapsed) != mSelectionCollapsed) {
UpdateCommandGroup(NS_LITERAL_STRING("select"));
mSelectionCollapsed = isCollapsed;
}
// isn't this redundant with the UpdateCommandGroup above?
// can we just nuke the above call? or create a meta command group?
UpdateCommandGroup(NS_LITERAL_STRING("style"));
mSelectionCollapsed = SelectionIsCollapsed();
UpdateCommandGroup(CommandGroup::Style);
}
nsresult ComposerCommandsUpdater::UpdateDirtyState(bool aNowDirty) {
if (mDirtyState != static_cast<int8_t>(aNowDirty)) {
UpdateCommandGroup(NS_LITERAL_STRING("save"));
UpdateCommandGroup(NS_LITERAL_STRING("undo"));
mDirtyState = aNowDirty;
}
return NS_OK;
}
nsresult ComposerCommandsUpdater::UpdateCommandGroup(
const nsAString& aCommandGroup) {
void ComposerCommandsUpdater::UpdateCommandGroup(CommandGroup aCommandGroup) {
RefPtr<nsCommandManager> commandManager = GetCommandManager();
NS_ENSURE_TRUE(commandManager, NS_ERROR_FAILURE);
if (aCommandGroup.EqualsLiteral("undo")) {
commandManager->CommandStatusChanged("cmd_undo");
commandManager->CommandStatusChanged("cmd_redo");
return NS_OK;
if (NS_WARN_IF(!commandManager)) {
return;
}
if (aCommandGroup.EqualsLiteral("select") ||
aCommandGroup.EqualsLiteral("style")) {
commandManager->CommandStatusChanged("cmd_bold");
commandManager->CommandStatusChanged("cmd_italic");
commandManager->CommandStatusChanged("cmd_underline");
commandManager->CommandStatusChanged("cmd_tt");
switch (aCommandGroup) {
case CommandGroup::Undo:
commandManager->CommandStatusChanged("cmd_undo");
commandManager->CommandStatusChanged("cmd_redo");
return;
case CommandGroup::Style:
commandManager->CommandStatusChanged("cmd_bold");
commandManager->CommandStatusChanged("cmd_italic");
commandManager->CommandStatusChanged("cmd_underline");
commandManager->CommandStatusChanged("cmd_tt");
commandManager->CommandStatusChanged("cmd_strikethrough");
commandManager->CommandStatusChanged("cmd_superscript");
commandManager->CommandStatusChanged("cmd_subscript");
commandManager->CommandStatusChanged("cmd_nobreak");
commandManager->CommandStatusChanged("cmd_strikethrough");
commandManager->CommandStatusChanged("cmd_superscript");
commandManager->CommandStatusChanged("cmd_subscript");
commandManager->CommandStatusChanged("cmd_nobreak");
commandManager->CommandStatusChanged("cmd_em");
commandManager->CommandStatusChanged("cmd_strong");
commandManager->CommandStatusChanged("cmd_cite");
commandManager->CommandStatusChanged("cmd_abbr");
commandManager->CommandStatusChanged("cmd_acronym");
commandManager->CommandStatusChanged("cmd_code");
commandManager->CommandStatusChanged("cmd_samp");
commandManager->CommandStatusChanged("cmd_var");
commandManager->CommandStatusChanged("cmd_em");
commandManager->CommandStatusChanged("cmd_strong");
commandManager->CommandStatusChanged("cmd_cite");
commandManager->CommandStatusChanged("cmd_abbr");
commandManager->CommandStatusChanged("cmd_acronym");
commandManager->CommandStatusChanged("cmd_code");
commandManager->CommandStatusChanged("cmd_samp");
commandManager->CommandStatusChanged("cmd_var");
commandManager->CommandStatusChanged("cmd_increaseFont");
commandManager->CommandStatusChanged("cmd_decreaseFont");
commandManager->CommandStatusChanged("cmd_increaseFont");
commandManager->CommandStatusChanged("cmd_decreaseFont");
commandManager->CommandStatusChanged("cmd_paragraphState");
commandManager->CommandStatusChanged("cmd_fontFace");
commandManager->CommandStatusChanged("cmd_fontColor");
commandManager->CommandStatusChanged("cmd_backgroundColor");
commandManager->CommandStatusChanged("cmd_highlight");
return NS_OK;
commandManager->CommandStatusChanged("cmd_paragraphState");
commandManager->CommandStatusChanged("cmd_fontFace");
commandManager->CommandStatusChanged("cmd_fontColor");
commandManager->CommandStatusChanged("cmd_backgroundColor");
commandManager->CommandStatusChanged("cmd_highlight");
return;
case CommandGroup::Save:
commandManager->CommandStatusChanged("cmd_setDocumentModified");
commandManager->CommandStatusChanged("cmd_save");
return;
default:
MOZ_ASSERT_UNREACHABLE("New command group hasn't been implemented yet");
}
if (aCommandGroup.EqualsLiteral("save")) {
// save commands (most are not in C++)
commandManager->CommandStatusChanged("cmd_setDocumentModified");
commandManager->CommandStatusChanged("cmd_save");
return NS_OK;
}
return NS_OK;
}
nsresult ComposerCommandsUpdater::UpdateOneCommand(const char* aCommand) {
@ -318,8 +247,7 @@ nsCommandManager* ComposerCommandsUpdater::GetCommandManager() {
return mDocShell->GetCommandManager();
}
NS_IMETHODIMP
ComposerCommandsUpdater::GetName(nsACString& aName) {
NS_IMETHODIMP ComposerCommandsUpdater::GetName(nsACString& aName) {
aName.AssignLiteral("ComposerCommandsUpdater");
return NS_OK;
}

View File

@ -9,7 +9,6 @@
#include "nsCOMPtr.h" // for already_AddRefed, nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsIDocumentStateListener.h"
#include "nsINamed.h"
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS
#include "nsITimer.h" // for NS_DECL_NSITIMERCALLBACK, etc
@ -24,8 +23,7 @@ class nsPIDOMWindowOuter;
namespace mozilla {
class ComposerCommandsUpdater final : public nsIDocumentStateListener,
public nsITransactionListener,
class ComposerCommandsUpdater final : public nsITransactionListener,
public nsITimerCallback,
public nsINamed {
public:
@ -34,10 +32,7 @@ class ComposerCommandsUpdater final : public nsIDocumentStateListener,
// nsISupports
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ComposerCommandsUpdater,
nsIDocumentStateListener)
// nsIDocumentStateListener
NS_DECL_NSIDOCUMENTSTATELISTENER
nsITransactionListener)
// nsITimerCallback
NS_DECL_NSITIMERCALLBACK
@ -55,6 +50,43 @@ class ComposerCommandsUpdater final : public nsIDocumentStateListener,
*/
void OnSelectionChange() { PrimeUpdateTimer(); }
/**
* OnHTMLEditorCreated() is called when `HTMLEditor` is created and
* initialized.
*/
MOZ_CAN_RUN_SCRIPT void OnHTMLEditorCreated() {
UpdateOneCommand("obs_documentCreated");
}
/**
* OnBeforeHTMLEditorDestroyed() is called when `HTMLEditor` is being
* destroyed.
*/
MOZ_CAN_RUN_SCRIPT void OnBeforeHTMLEditorDestroyed() {
// cancel any outstanding update timer
if (mUpdateTimer) {
mUpdateTimer->Cancel();
mUpdateTimer = nullptr;
}
// We can't notify the command manager of this right now; it is too late in
// some cases and the window is already partially destructed (e.g. JS
// objects may be gone).
}
/**
* OnHTMLEditorDirtyStateChanged() is called when dirty state of `HTMLEditor`
* is changed form or to "dirty".
*/
MOZ_CAN_RUN_SCRIPT void OnHTMLEditorDirtyStateChanged(bool aNowDirty) {
if (mDirtyState == static_cast<int8_t>(aNowDirty)) {
return;
}
UpdateCommandGroup(CommandGroup::Save);
UpdateCommandGroup(CommandGroup::Undo);
mDirtyState = aNowDirty;
}
protected:
virtual ~ComposerCommandsUpdater();
@ -65,9 +97,13 @@ class ComposerCommandsUpdater final : public nsIDocumentStateListener,
};
bool SelectionIsCollapsed();
nsresult UpdateDirtyState(bool aNowDirty);
nsresult UpdateOneCommand(const char* aCommand);
nsresult UpdateCommandGroup(const nsAString& aCommandGroup);
MOZ_CAN_RUN_SCRIPT nsresult UpdateOneCommand(const char* aCommand);
enum class CommandGroup {
Save,
Style,
Undo,
};
MOZ_CAN_RUN_SCRIPT void UpdateCommandGroup(CommandGroup aCommandGroup);
nsCommandManager* GetCommandManager();

View File

@ -335,7 +335,7 @@ nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
if (mEditorStatus != eEditorCreationInProgress) {
RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater;
updater->NotifyDocumentCreated();
updater->OnHTMLEditorCreated();
// At this point we have made a final decision that we don't support
// editing the current document. This is an internal failure state, but
@ -411,8 +411,7 @@ nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
// Set up as a doc state listener
// Important! We must have this to broadcast the "obs_documentCreated" message
rv = htmlEditor->AddDocumentStateListener(mComposerCommandsUpdater);
NS_ENSURE_SUCCESS(rv, rv);
htmlEditor->SetComposerCommandsUpdater(mComposerCommandsUpdater);
rv = htmlEditor->Init(*doc, nullptr /* root content */, nullptr, mEditorFlags,
EmptyString());
@ -423,8 +422,6 @@ nsresult nsEditingSession::SetupEditorOnWindow(nsPIDOMWindowOuter& aWindow) {
return NS_ERROR_FAILURE;
}
htmlEditor->SetComposerCommandsUpdater(mComposerCommandsUpdater);
// and as a transaction listener
MOZ_ASSERT(mComposerCommandsUpdater);
DebugOnly<bool> addedTransactionListener =
@ -452,7 +449,6 @@ void nsEditingSession::RemoveListenersAndControllers(
// Remove all the listeners
aHTMLEditor->SetComposerCommandsUpdater(nullptr);
aHTMLEditor->RemoveDocumentStateListener(mComposerCommandsUpdater);
DebugOnly<bool> removedTransactionListener =
aHTMLEditor->RemoveTransactionListener(*mComposerCommandsUpdater);
NS_WARNING_ASSERTION(removedTransactionListener,

View File

@ -25,6 +25,7 @@
#include "PlaceholderTransaction.h" // for PlaceholderTransaction
#include "SplitNodeTransaction.h" // for SplitNodeTransaction
#include "mozilla/CheckedInt.h" // for CheckedInt
#include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
#include "mozilla/ComputedStyle.h" // for ComputedStyle
#include "mozilla/CSSEditUtils.h" // for CSSEditUtils
#include "mozilla/EditAction.h" // for EditSubAction
@ -2883,37 +2884,44 @@ nsINode* EditorBase::GetFirstEditableNode(nsINode* aRoot) {
nsresult EditorBase::NotifyDocumentListeners(
TDocumentListenerNotification aNotificationType) {
if (!mDocStateListeners.Length()) {
// Maybe there just aren't any.
RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
AsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr;
if (!mDocStateListeners.Length() && !composerCommandsUpdate) {
return NS_OK;
}
AutoDocumentStateListenerArray listeners(mDocStateListeners);
nsresult rv = NS_OK;
switch (aNotificationType) {
case eDocumentCreated:
if (composerCommandsUpdate) {
composerCommandsUpdate->OnHTMLEditorCreated();
}
for (auto& listener : listeners) {
rv = listener->NotifyDocumentCreated();
if (NS_FAILED(rv)) {
break;
nsresult rv = listener->NotifyDocumentCreated();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
break;
return NS_OK;
case eDocumentToBeDestroyed:
if (composerCommandsUpdate) {
composerCommandsUpdate->OnBeforeHTMLEditorDestroyed();
}
for (auto& listener : listeners) {
rv = listener->NotifyDocumentWillBeDestroyed();
if (NS_FAILED(rv)) {
break;
nsresult rv = listener->NotifyDocumentWillBeDestroyed();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
break;
return NS_OK;
case eDocumentStateChanged: {
bool docIsDirty;
rv = GetDocumentModified(&docIsDirty);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = GetDocumentModified(&docIsDirty);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (static_cast<int8_t>(docIsDirty) == mDocDirtyState) {
return NS_OK;
@ -2921,19 +2929,21 @@ nsresult EditorBase::NotifyDocumentListeners(
mDocDirtyState = docIsDirty;
if (composerCommandsUpdate) {
composerCommandsUpdate->OnHTMLEditorDirtyStateChanged(mDocDirtyState);
}
for (auto& listener : listeners) {
rv = listener->NotifyDocumentStateChanged(mDocDirtyState);
if (NS_FAILED(rv)) {
break;
nsresult rv = listener->NotifyDocumentStateChanged(mDocDirtyState);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
break;
return NS_OK;
}
default:
MOZ_ASSERT_UNREACHABLE("Unknown notification");
return NS_ERROR_FAILURE;
}
return rv;
}
nsresult EditorBase::SetTextNodeWithoutTransaction(const nsAString& aString,

View File

@ -2839,9 +2839,8 @@ class EditorBase : public nsIEditor,
AutoEditorObserverArray;
AutoEditorObserverArray mEditorObservers;
// Listen to overall doc state (dirty or not, just created, etc.).
// Document state listener is currently used by EditingSession and
// BlueGriffon so that reserving only one is enough (although, this is not
// necessary for TextEditor).
// Document state listener is currently used by FinderHighlighter and
// BlueGriffon so that reserving only one is enough.
typedef AutoTArray<OwningNonNull<nsIDocumentStateListener>, 1>
AutoDocumentStateListenerArray;
AutoDocumentStateListenerArray mDocStateListeners;