CreateBogusNodeIfNeeded() is too expensive

RESOLVED FIXED in Firefox 57

Status

()

Core
Editor
RESOLVED FIXED
6 months ago
6 months ago

People

(Reporter: Away for a while, Assigned: Away for a while)

Tracking

(Blocks: 1 bug)

unspecified
mozilla57
Points:
---
Dependency tree / graph

Firefox Tracking Flags

(firefox57 fixed)

Details

Attachments

(5 attachments, 5 obsolete attachments)

1.88 KB, patch
masayuki
: review+
Details | Diff | Splinter Review
1.28 KB, patch
masayuki
: review+
Details | Diff | Splinter Review
1.12 KB, patch
masayuki
: review+
Details | Diff | Splinter Review
6.69 KB, patch
masayuki
: review+
Details | Diff | Splinter Review
4.56 KB, patch
masayuki
: review+
Details | Diff | Splinter Review
(Assignee)

Description

6 months ago
There are a number of easy improvements we can make here.
(Assignee)

Comment 1

6 months ago
Created attachment 8892728 [details] [diff] [review]
Part 1: Inline EditorBase::IsMozEditorBogusNode()
Attachment #8892728 - Flags: review?(masayuki)
(Assignee)

Comment 2

6 months ago
Created attachment 8892729 [details] [diff] [review]
Part 2: Hoist the body editablity check out of the loop
Attachment #8892729 - Flags: review?(masayuki)
(Assignee)

Comment 3

6 months ago
Created attachment 8892730 [details] [diff] [review]
Part 3: Devirtualize and inline EditorBase::IsEditable()
Attachment #8892730 - Flags: review?(masayuki)
(Assignee)

Comment 4

6 months ago
Created attachment 8892731 [details] [diff] [review]
Part 4: Avoid manipulating the refcount of all visited nodes in CreateBogusNodeIfNeeded()
Attachment #8892731 - Flags: review?(masayuki)
(Assignee)

Comment 5

6 months ago
Created attachment 8892732 [details] [diff] [review]
Part 5: Avoid collapsing the selection at the end of CreateBogusNodeIfNeeded() when called from TextEditRules::AfterEdit()

In this case we are soon going to collapse the selection anyway, so there
is no reason to collapse it once here also.
Attachment #8892732 - Flags: review?(masayuki)
Attachment #8892728 - Flags: review?(masayuki) → review+
Attachment #8892729 - Flags: review?(masayuki) → review+
Comment on attachment 8892730 [details] [diff] [review]
Part 3: Devirtualize and inline EditorBase::IsEditable()

>+  bool IsEditable(nsINode* aNode)
>+  {
>+    NS_ENSURE_TRUE(aNode, false);
>+
>+    if (!aNode->IsNodeOfType(nsINode::eCONTENT) || IsMozEditorBogusNode(aNode) ||
>+        !IsModifiableNode(aNode)) {
>+      return false;
>+    }
>+
>+    switch (aNode->NodeType()) {
>+      case nsIDOMNode::ELEMENT_NODE:
>+        // In HTML editors, if we're dealing with an element, then ask it
>+        // whether it's editable.
>+        return IsPlaintextEditor() ? true : aNode->IsEditable();

No, this changes the behavior. IsPlaintextEditor() may return true even if the instance is HTMLEditor. E.g., plaintext email editor of Thunderbird.

So, |!AsHTMLEditor()| returns what you want.  However, it's a virtual method. So, you might want to create a bool member which indicates whether it's an HTMLEditor instance.
Attachment #8892730 - Flags: review?(masayuki) → review-
Attachment #8892731 - Flags: review?(masayuki) → review+
Comment on attachment 8892732 [details] [diff] [review]
Part 5: Avoid collapsing the selection at the end of CreateBogusNodeIfNeeded() when called from TextEditRules::AfterEdit()

> nsresult
>-TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection)
>+TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection, bool aSkipSelectionCollapse)

nit: over 80 characters. But please see next point.

>@@ -1465,18 +1465,20 @@ TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection)
>   // Give it a special attribute.
>   newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
>                       kMOZEditorBogusNodeValue, false);
> 
>   // Put the node in the document.
>   nsresult rv = mTextEditor->InsertNode(*mBogusNode, *body, 0);
>   NS_ENSURE_SUCCESS(rv, rv);
> 
>-  // Set selection.
>-  aSelection->Collapse(body, 0);
>+  if (!aSkipSelectionCollapse) {
>+    // Set selection.
>+    aSelection->Collapse(body, 0);
>+  }

I don't like this change since additional bool argument isn't clear what it means when I see the callers.

How about to remove this collapsing simply and do it by callers if it's necessary? There are only 4 callers and the method name, CreateBogusNodeIfNeeded, doesn't sound like that it will change selection.
Attachment #8892732 - Flags: review?(masayuki) → review-
(Assignee)

Comment 8

6 months ago
(In reply to Masayuki Nakano [:masayuki] (JST, +0900) from comment #7)
> Comment on attachment 8892732 [details] [diff] [review]
> Part 5: Avoid collapsing the selection at the end of
> CreateBogusNodeIfNeeded() when called from TextEditRules::AfterEdit()
> 
> > nsresult
> >-TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection)
> >+TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection, bool aSkipSelectionCollapse)
> 
> nit: over 80 characters. But please see next point.
> 
> >@@ -1465,18 +1465,20 @@ TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection)
> >   // Give it a special attribute.
> >   newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
> >                       kMOZEditorBogusNodeValue, false);
> > 
> >   // Put the node in the document.
> >   nsresult rv = mTextEditor->InsertNode(*mBogusNode, *body, 0);
> >   NS_ENSURE_SUCCESS(rv, rv);
> > 
> >-  // Set selection.
> >-  aSelection->Collapse(body, 0);
> >+  if (!aSkipSelectionCollapse) {
> >+    // Set selection.
> >+    aSelection->Collapse(body, 0);
> >+  }
> 
> I don't like this change since additional bool argument isn't clear what it
> means when I see the callers.
> 
> How about to remove this collapsing simply and do it by callers if it's
> necessary? There are only 4 callers and the method name,
> CreateBogusNodeIfNeeded, doesn't sound like that it will change selection.

I thought about doing that first but the issue is that we don't have access to the body at the callers and getting it again seems like wasted work.  How about adding an enum or something like that?
Adding enum sounds good.

On the other hand, the |body| is result of GetRoot().
https://searchfox.org/mozilla-central/rev/f0e4ae5f8c40ba742214e89aba3f554da0b89a33/editor/libeditor/EditorBase.cpp#4981,4983,4986,4989
Its result is cached at first use. So, I don't think that this causes performance issue.
(Assignee)

Comment 10

6 months ago
(In reply to Masayuki Nakano [:masayuki] (JST, +0900) from comment #9)
> On the other hand, the |body| is result of GetRoot().
> https://searchfox.org/mozilla-central/rev/
> f0e4ae5f8c40ba742214e89aba3f554da0b89a33/editor/libeditor/EditorBase.
> cpp#4981,4983,4986,4989
> Its result is cached at first use. So, I don't think that this causes
> performance issue.

You're right, good point.  I'll do that!  That is actually my preferred approach as well.  It's always better make it more obvious where expensive work happens.
(Assignee)

Comment 11

6 months ago
(In reply to Masayuki Nakano [:masayuki] (JST, +0900) from comment #6)
> So, |!AsHTMLEditor()| returns what you want.  However, it's a virtual
> method. So, you might want to create a bool member which indicates whether
> it's an HTMLEditor instance.

Even better, I'm going to devirtualize AsHTMLEditor() first (so that it's more efficient for all consumers) and then use !AsHTMLEditor() here.  :-)
(Assignee)

Comment 12

6 months ago
Created attachment 8893576 [details] [diff] [review]
Part 3: Devirtualize EditorBase::AsTextEditor()/AsHTMLEditor()
Attachment #8893576 - Flags: review?(masayuki)
(Assignee)

Comment 13

6 months ago
Created attachment 8893577 [details] [diff] [review]
Part 4: Devirtualize and inline EditorBase::IsEditable()
Attachment #8893577 - Flags: review?(masayuki)
(Assignee)

Updated

6 months ago
Attachment #8892730 - Attachment is obsolete: true
(Assignee)

Comment 14

6 months ago
Created attachment 8893578 [details] [diff] [review]
Part 6: Avoid collapsing the selection at the end of CreateBogusNodeIfNeeded() when called from TextEditRules::AfterEdit()

In this case we are soon going to collapse the selection anyway, so there
is no reason to collapse it once here also.
Attachment #8893578 - Flags: review?(masayuki)
(Assignee)

Updated

6 months ago
Attachment #8892732 - Attachment is obsolete: true
Comment on attachment 8893576 [details] [diff] [review]
Part 3: Devirtualize EditorBase::AsTextEditor()/AsHTMLEditor()

>diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h
>index d014c8f40a86..5243f76576ad 100644
>--- a/editor/libeditor/EditorBase.h
>+++ b/editor/libeditor/EditorBase.h
>@@ -232,20 +232,22 @@ public:
>   };
> 
>   /**
>    * The default constructor. This should suffice. the setting of the
>    * interfaces is done after the construction of the editor class.
>    */
>   EditorBase();
> 
>-  virtual TextEditor* AsTextEditor() = 0;
>-  virtual const TextEditor* AsTextEditor() const = 0;
>-  virtual HTMLEditor* AsHTMLEditor() = 0;
>-  virtual const HTMLEditor* AsHTMLEditor() const = 0;
>+  // Please include TextEditor.h.
>+  inline TextEditor* AsTextEditor();
>+  inline const TextEditor* AsTextEditor() const;
>+  // Please include HTMLEditor.h.
>+  inline HTMLEditor* AsHTMLEditor();
>+  inline const HTMLEditor* AsHTMLEditor() const;
> 
> protected:
>   /**
>    * The default destructor. This should suffice. Should this be pure virtual
>    * for someone to derive from the EditorBase later? I don't believe so.
>    */
>   virtual ~EditorBase();
> 
>@@ -1219,16 +1221,18 @@ protected:
>   bool mDidPostCreate;
>   bool mDispatchInputEvent;
>   // True while the instance is handling an edit action.
>   bool mIsInEditAction;
>   // Whether caret is hidden forcibly.
>   bool mHidingCaret;
>   // Whether spellchecker dictionary is initialized after focused.
>   bool mSpellCheckerDictionaryUpdated;
>+  // Whether we are a plaintext editor class.
>+  bool mIsPlaintextEditorClass;

The word, "PlaintextEditor" is used for nsIPlaintextEditor and the editing mode of HTMLEditor. So, could you rename this to mIsTextEditorClass?

>diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
>index 9b7acee608a1..87e42f6b00fe 100644
>--- a/editor/libeditor/HTMLEditor.cpp
>+++ b/editor/libeditor/HTMLEditor.cpp
>@@ -127,16 +127,17 @@ HTMLEditor::HTMLEditor()
>   , mPositionedObjectMarginTop(0)
>   , mPositionedObjectBorderLeft(0)
>   , mPositionedObjectBorderTop(0)
>   , mGridSize(0)
>   , mDefaultParagraphSeparator(
>       Preferences::GetBool("editor.use_div_for_default_newlines", true)
>       ? ParagraphSeparator::div : ParagraphSeparator::br)
> {
>+  mIsPlaintextEditorClass = false;

Hmm, so, this means that, while TextEditor::TextEditor() and EditorBase::EditorBase() are running, AsHTMLEditor() returns nullptr? Shouldn't we create another constructor to initialize the bool flag as expected?

>diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h
>@@ -1055,11 +1052,21 @@ private:
>    */
>   already_AddRefed<Element> CreateAnonymousElement(
>                               nsIAtom* aTag,
>                               nsIDOMNode* aParentNode,
>                               const nsAString& aAnonClass,
>                               bool aIsCreatedHidden);
> };
> 
>+inline HTMLEditor* EditorBase::AsHTMLEditor()
>+{
>+  return mIsPlaintextEditorClass ? nullptr : static_cast<HTMLEditor*>(this);
>+}
>+
>+inline const HTMLEditor* EditorBase::AsHTMLEditor() const
>+{
>+  return mIsPlaintextEditorClass ? nullptr : static_cast<const HTMLEditor*>(this);
>+}

Wow, tricky! HTMLEditor.h will have the implementation of EditorBase.

Do you need |inline| keyword here? You specified it in EditorBase.h.

And could you wrap after |HTMLEditor*|? I mean the method should begins with:


HTMLEditor*
EditorBase::AsHTMLEditor()
{

and

const HTMLEditor*
EditorBase::AsHTMLEditor() const
{

or

inline HTMLEditor*
EditorBase::AsHTMLEditor()
{

and

inline const HTMLEditor*
EditorBase::AsHTMLEditor() const
{

>diff --git a/editor/libeditor/TextEditor.h b/editor/libeditor/TextEditor.h
>@@ -242,11 +237,21 @@ protected:
>   int32_t mNewlineHandling;
>   int32_t mCaretStyle;
> 
>   friend class AutoEditInitRulesTrigger;
>   friend class HTMLEditRules;
>   friend class TextEditRules;
> };
> 
>+inline TextEditor* EditorBase::AsTextEditor()

Same as AsHTMLEditor(), please wrap this line after |TextEditor*|.

>+{
>+  return mIsPlaintextEditorClass ? static_cast<TextEditor*>(this) : nullptr;

No, this is wrong. Even if it's HTMLEditor instance, it implements TextEditor too. So, this should be:

return static_cast<TextEditor*>(this);

>+}
>+
>+inline const TextEditor* EditorBase::AsTextEditor() const

Same. Please wrap this line after |const TextEditor*|.

>+{
>+  return mIsPlaintextEditorClass ? static_cast<const TextEditor*>(this) : nullptr;

Same. This is wrong. Should be:

return static_cast<const TextEditor*>(this);


Ah, from the point of view from these methods, mIsPlaintextEditorClass (mIsTextEditorClass) shold be mIsHTMLEditorClass?
Attachment #8893576 - Flags: review?(masayuki) → review-
Comment on attachment 8893577 [details] [diff] [review]
Part 4: Devirtualize and inline EditorBase::IsEditable()

If you'll rename mIsPlaintextEditorClass to mIsHTMLEditorClass, I should check if you revert bool value check here. So, canceling the review request to review new patch.
Attachment #8893577 - Flags: review?(masayuki)
Attachment #8893578 - Flags: review?(masayuki) → review+
(Assignee)

Comment 17

6 months ago
(In reply to Masayuki Nakano [:masayuki] (JST, +0900) from comment #15)
> >+  // Whether we are a plaintext editor class.
> >+  bool mIsPlaintextEditorClass;
> 
> The word, "PlaintextEditor" is used for nsIPlaintextEditor and the editing
> mode of HTMLEditor. So, could you rename this to mIsTextEditorClass?

Sure.

> > {
> >+  mIsPlaintextEditorClass = false;
> 
> Hmm, so, this means that, while TextEditor::TextEditor() and
> EditorBase::EditorBase() are running, AsHTMLEditor() returns nullptr?

Yes.

> Shouldn't we create another constructor to initialize the bool flag as
> expected?

Not unless we are seeking a behavior change here.  I just implemented what we currently do.

According to the C++ standard, when creating a Derived object, as the Base constructor is running, the identity of *this is Base, not Derived.  The identity will switch over to become Derived when the ctor for Derived starts to run.  Indeed, it is the constructor that switches the vtable pointer to point to the vtable of the class, so if you call a Base virtual function that Derived overrides inside the Base constructor, you will call the Base version.

So in the example above, currently inside EditorBase::EditorBase() when you call AsHTMLEditor(), you are guaranteed to run EditorBase::AsHTMLEditor() which returns false.

> >diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h
> >@@ -1055,11 +1052,21 @@ private:
> >    */
> >   already_AddRefed<Element> CreateAnonymousElement(
> >                               nsIAtom* aTag,
> >                               nsIDOMNode* aParentNode,
> >                               const nsAString& aAnonClass,
> >                               bool aIsCreatedHidden);
> > };
> > 
> >+inline HTMLEditor* EditorBase::AsHTMLEditor()
> >+{
> >+  return mIsPlaintextEditorClass ? nullptr : static_cast<HTMLEditor*>(this);
> >+}
> >+
> >+inline const HTMLEditor* EditorBase::AsHTMLEditor() const
> >+{
> >+  return mIsPlaintextEditorClass ? nullptr : static_cast<const HTMLEditor*>(this);
> >+}
> 
> Wow, tricky! HTMLEditor.h will have the implementation of EditorBase.

Yeah, I had to do it like this for the static_cast's to work...  (Sorry, it's kind of gross.)

> Do you need |inline| keyword here? You specified it in EditorBase.h.

That is a good question.  We do this in a whole bunch of other places, but I just checked cppreference.com and apparently this is only needed on declarations, so should be OK to drop here.

> And could you wrap after |HTMLEditor*|? I mean the method should begins with:

Of course.

> >+{
> >+  return mIsPlaintextEditorClass ? static_cast<TextEditor*>(this) : nullptr;
> 
> No, this is wrong. Even if it's HTMLEditor instance, it implements
> TextEditor too. So, this should be:
> 
> return static_cast<TextEditor*>(this);

Aha, right.  I misunderstood the meaning of this function, sorry about that!

> Ah, from the point of view from these methods, mIsPlaintextEditorClass
> (mIsTextEditorClass) shold be mIsHTMLEditorClass?

Yeah I suppose that makes more sense now in hindsight.
(Assignee)

Comment 18

6 months ago
Comment on attachment 8893578 [details] [diff] [review]
Part 6: Avoid collapsing the selection at the end of CreateBogusNodeIfNeeded() when called from TextEditRules::AfterEdit()

After testing this on try, it turns out that I was wrong, and this isn't a no-op.  Apparently there are existing tests that rely on this behavior in various ways, and this part breaks them, so I will retract this part of the series.
Attachment #8893578 - Attachment is obsolete: true
(Assignee)

Comment 19

6 months ago
Created attachment 8893660 [details] [diff] [review]
Part 3: Devirtualize EditorBase::AsTextEditor()/AsHTMLEditor()
Attachment #8893660 - Flags: review?(masayuki)
(Assignee)

Updated

6 months ago
Attachment #8893576 - Attachment is obsolete: true
(Assignee)

Comment 20

6 months ago
Created attachment 8893661 [details] [diff] [review]
Part 4: Devirtualize and inline EditorBase::IsEditable()
Attachment #8893661 - Flags: review?(masayuki)
(Assignee)

Updated

6 months ago
Attachment #8893577 - Attachment is obsolete: true
Attachment #8893660 - Flags: review?(masayuki) → review+
Attachment #8893661 - Flags: review?(masayuki) → review+
When I only applied part3 to my local tree, I got this error on Windows.
> 2:46.14 Unified_cpp_dom_base6.obj : error LNK2019: unresolved external symbol "public: class mozilla::HTMLEditor * __cdecl mozilla::EditorBase::AsHTMLEditor(void)" (?AsHTMLEditor@EditorBase@mozilla@@QEAAPEAVHTMLEditor@2@XZ) referenced in function "public: class nsIContent * __cdecl nsINode::GetTextEditorRootContent(class mozilla::TextEditor * *)" (?GetTextEditorRootContent@nsINode@@QEAAPEAVnsIContent@@PEAPEAVTextEditor@mozilla@@@Z)
Okay, please include mozilla/HTMLEditor.h in dom/base/nsINode.cpp.

Comment 23

6 months ago
Pushed by eakhgari@mozilla.com:
https://hg.mozilla.org/integration/mozilla-inbound/rev/3c630870cc7c
Part 1: Inline EditorBase::IsMozEditorBogusNode(); r=masayuki
https://hg.mozilla.org/integration/mozilla-inbound/rev/2a06201254fd
Part 2: Hoist the body editablity check out of the loop; r=masayuki
https://hg.mozilla.org/integration/mozilla-inbound/rev/9bc5ba5a521a
Part 3: Devirtualize EditorBase::AsTextEditor()/AsHTMLEditor(); r=masayuki
https://hg.mozilla.org/integration/mozilla-inbound/rev/7a07154c27e9
Part 4: Devirtualize and inline EditorBase::IsEditable(); r=masayuki
https://hg.mozilla.org/integration/mozilla-inbound/rev/b605b5224f61
Part 5: Avoid manipulating the refcount of all visited nodes in CreateBogusNodeIfNeeded(); r=masayuki
You need to log in before you can comment on or make changes to this bug.