Closed Bug 131775 Opened 22 years ago Closed 21 years ago

[FIX]BeginUpdate is not always matched by EndUpdate in DOM code

Categories

(Core :: DOM: Core & HTML, defect, P1)

defect

Tracking

()

RESOLVED FIXED
mozilla1.6alpha

People

(Reporter: bzbarsky, Assigned: bzbarsky)

References

Details

Attachments

(2 files, 2 obsolete files)

We have a few places in our code where we BeginUpdate() but may not EndUpdate():

nsSVGAttributes::SetAttr
nsSVGAttributes::UnsetAttr
DOMMediaListImpl::BeginMediaChange/DOMMediaListImpl::EndMediaChange (the latter
   is not always called by the medialist manipulation methods)
DOMCSSDeclarationImpl::ParsePropertyValue
CSSStyleSheetImpl::InsertRule
CSSStyleSheetImpl::DeleteRule
CSSStyleSheetImpl::DeleteRuleFromGroup
CSSStyleSheetImpl::InsertRuleIntoGroup
nsGenericContainerElement::UnsetAttr
nsGenericContainerElement::SetAttr

Not assigning this to myself because I don't have time to do this till sometime
in mid-May and some of that code is hairy, but feel free to punt to me if this
is not a priority and if you're not going to get to it till then anyway, jst.
Mass-reassigning bugs to dom_bugs@netscape.com
Assignee: jst → dom_bugs
I guess I may as well take this....
Assignee: dom_bugs → bzbarsky
Should we write a little nsAutoUpdate (or whatever) class to help here?
Yeah, that was what I was thinking...  I was gonna call it 
nsContentUpdateBatch, maybe... (or just nsUpdateBatch).
Priority: -- → P1
Target Milestone: --- → mozilla1.5alpha
Depends on: 200931
Attached patch Patch (obsolete) — Splinter Review
This introduces the mozAutoDocUpdate class and uses it in various places.
Attached patch Same as diff -w FOR REVIEW ONLY (obsolete) — Splinter Review
Comment on attachment 132561 [details] [diff] [review]
Same as diff -w FOR REVIEW ONLY

reviews?
Attachment #132561 - Flags: superreview?(jst)
Attachment #132561 - Flags: review?(caillon)
Incidentally, this fixes bug 211022 
Blocks: 211022
Summary: BeginUpdate is not always matched by EndUpdate in DOM code → [FIX]BeginUpdate is not always matched by EndUpdate in DOM code
Target Milestone: mozilla1.5alpha → mozilla1.6alpha
Comment on attachment 132561 [details] [diff] [review]
Same as diff -w FOR REVIEW ONLY

This patch is bogus...
Attachment #132561 - Flags: superreview?(jst)
Attachment #132561 - Flags: review?(caillon)
Attachment #132560 - Attachment is obsolete: true
Attachment #132561 - Attachment is obsolete: true
Attachment #132623 - Flags: superreview?(jst)
Attachment #132623 - Flags: review?(caillon)
Attachment #132561 - Flags: superreview?(jst)
Attachment #132561 - Flags: review?(caillon)
Attachment #132561 - Flags: superreview?(jst)
Attachment #132561 - Flags: review?(caillon)
Comment on attachment 132623 [details] [diff] [review]
Same as diff -w FOR REVIEW ONLY

General comment:

It seems there are quite a few callsites, (though most prominent in
nsSVGElement) where things are similar to:


  A::DoFoo()
  {
    mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, aNotify);
    if (foo) {
      // Do things which will send notifications
    }
  }

I think that in these cases, you can either use early returns if (!foo) since
nothing will happen to get rid of the extra scope, or move the mozAutoDocUpdate
inside the if so that we don't tell the document there will be udpates when
there really won't be.

There are cases in other places where you can't do an early return, so I would
just recommend changing the scope.  Let's send as few notifications to
observers as possible.	:-)



>Index: content/base/public/nsIDocument.h

>   // Observation hooks used to propagate notifications to document observers.
>   // BeginUpdate must be called before any batch of modifications of the
>+  // content model or of style data, EndUpdate must be called afterward.  To
>+  // make this easy and painless, use the mozAutoDocUpdate helper class.  

Hm, I never thought I would make a nit this anal, but...

Could you move the "To" in the above comment down to the next line?  It seems
to dangle out there.  Sorry, I used to work at a newspaper a few years back and
that was one of the guidelines of making things more readable we tried to
follow (not ending a line with the first word of a multi-word statement when
said word is a short one), and I tend to agree that it makes it easier to read.



>   NS_IMETHOD BeginUpdate(nsUpdateType aUpdateType) = 0;
>   NS_IMETHOD EndUpdate(nsUpdateType aUpdateType) = 0;
>   NS_IMETHOD BeginLoad() = 0;
>   NS_IMETHOD EndLoad() = 0;
>   NS_IMETHOD ContentChanged(nsIContent* aContent,
>                             nsISupports* aSubContent) = 0;
>   // notify that one or two content nodes changed state
>   // either may be nsnull, but not both
>   NS_IMETHOD ContentStatesChanged(nsIContent* aContent1,
>                                   nsIContent* aContent2,
>@@ -467,20 +469,43 @@ public:
>                                const nsAString& Standalone) = 0;
>   NS_IMETHOD GetXMLDeclaration(nsAString& aVersion,
>                                nsAString& aEncoding,
>                                nsAString& Standalone) = 0;
> 
>   NS_IMETHOD_(PRBool) IsCaseSensitive() = 0;
> 
>   NS_IMETHOD_(PRBool) IsScriptEnabled() = 0;
> };
> 
>+class mozAutoDocUpdate {

Brace on its own line?	Also, could you please document we allow mDocument to
be null so that we don't have to null-check (and therefore add another scope)
in all the callers?

>+public:
>+  mozAutoDocUpdate(nsIDocument* aDocument, nsUpdateType aUpdateType,
>+                   PRBool aNotify) :
>+    mDocument(aNotify ? aDocument : nsnull),
>+    mUpdateType(aUpdateType)
>+  {
>+    if (mDocument) {
>+      mDocument->BeginUpdate(mUpdateType);
>+    }
>+  }
>+
>+  ~mozAutoDocUpdate()
>+  {
>+    if (mDocument) {
>+      mDocument->EndUpdate(mUpdateType);
>+    }
>+  }
>+
>+private:
>+  nsCOMPtr<nsIDocument> mDocument;
>+  nsUpdateType mUpdateType;
>+};
> 
> // XXX These belong somewhere else
> nsresult
> NS_NewHTMLDocument(nsIDocument** aInstancePtrResult);
> 
> nsresult
> NS_NewXMLDocument(nsIDocument** aInstancePtrResult);
> 
> #ifdef MOZ_SVG
> nsresult


>Index: content/base/src/nsGenericElement.cpp
>===================================================================
>RCS file: /cvsroot/mozilla/content/base/src/nsGenericElement.cpp,v
>retrieving revision 3.291
>diff -p -u -1 -0 -w -r3.291 nsGenericElement.cpp
>--- content/base/src/nsGenericElement.cpp	2 Oct 2003 21:29:31 -0000	3.291
>+++ content/base/src/nsGenericElement.cpp	4 Oct 2003 08:23:41 -0000
>@@ -2606,23 +2606,24 @@ nsGenericElement::doInsertBefore(nsIDOMN
>     NS_ENSURE_TRUE(doc_fragment, NS_ERROR_UNEXPECTED);
> 
>     doc_fragment->DisconnectChildren();
> 
>     PRUint32 count = newContent->GetChildCount();
> 
>     PRUint32 old_count = GetChildCount();
> 
>     PRBool do_notify = !!aRefChild;
> 
>-    if (count && !do_notify && mDocument) {
>-      mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
>-    }    
>+    // If do_notify is true, then we don't have to handle the
>+    // notifications ourselves...  So pass !do_notify as aNotify.

I guess technically, you're only passing !do_notify if there are children. 
Also is the reader of the comment supposed to know where |aNotify| comes from?  

>+    mozAutoDocUpdate updateBatch(mDocument, UPDATE_CONTENT_MODEL,
>+                                 count && !do_notify);


>Index: content/html/style/src/nsCSSStyleSheet.cpp

>+#define BEGIN_MEDIA_CHANGE()                                   \
>+  if (mStyleSheet) {                                           \
>+    rv = mStyleSheet->GetOwningDocument(*getter_AddRefs(doc)); \
>+    NS_ENSURE_SUCCESS(rv, rv);                                 \
>+  }                                                            \
>+  mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, PR_TRUE);    \
>+  if (mStyleSheet) {                                           \
>+    rv = mStyleSheet->WillDirty();                             \
>+    NS_ENSURE_SUCCESS(rv, rv);                                 \
>+  }
>+
>+#define END_MEDIA_CHANGE()                                     \
>+  if (mStyleSheet) {                                           \
>+    mStyleSheet->DidDirty();                                   \
>+  }                                                            \
>+  /* XXXldb Pass something meaningful? */                      \
>+  if (doc) {                                                   \
>+    rv = doc->StyleRuleChanged(mStyleSheet, nsnull, nsnull);   \
>+    NS_ENSURE_SUCCESS(rv, rv);                                 \
>+  }
>+

How about making mStyleSheet and doc be parameters to the macros?  This way it
makes the call-sites more explicit as to what is going on, and doesn't rely on
a local being defined |doc| at each call site, though it doesn't really matter
much since you name the variable the same thing everywhere....	Does this also
need PR_BEGIN_MACRO and PR_END_MACRO?
Oh duh, can't use PR_BEGIN/END_MACRO because of scoping.
> though most prominent in nsSVGElement

There is only one callsite in nsSVGElement where I could change the scope (in
nsSVGElement::RemoveChildAt).  The rest are correct, in fact.

Added comments about mozAutoDocUpdate.

> Also is the reader of the comment supposed to know where |aNotify| comes from?  

Made comment clearer.

> How about making mStyleSheet and doc be parameters to the macros?

Good idea.  Done.
Comment on attachment 132623 [details] [diff] [review]
Same as diff -w FOR REVIEW ONLY

(btw, that was r= with comments fixed)
Attachment #132623 - Flags: review?(caillon) → review+
Comment on attachment 132623 [details] [diff] [review]
Same as diff -w FOR REVIEW ONLY

sr=jst
Attachment #132623 - Flags: superreview?(jst) → superreview+
Checked in, with caillon's review comments addressed.
Status: NEW → RESOLVED
Closed: 21 years ago
Resolution: --- → FIXED
Component: DOM → DOM: Core & HTML
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: