Closed
Bug 610990
Opened 14 years ago
Closed 14 years ago
Regression: SVGPathSegList should allow manipulation of invalid paths
Categories
(Core :: SVG, defect)
Core
SVG
Tracking
()
VERIFIED
FIXED
mozilla2.0b11
Tracking | Status | |
---|---|---|
blocking2.0 | --- | final+ |
People
(Reporter: dholbert, Assigned: jwatt)
References
()
Details
(Keywords: regression, Whiteboard: [softblocker][fx4-fixed-bugday])
Attachments
(3 files, 6 obsolete files)
STEPS TO REPRODUCE:
1. Load http://svgtorture.googlecode.com/svn/trunk/automated/test.html
EXPECTED RESULTS: We fail test #9, #11, and no others.
ACTUAL RESULTS: We recently started failing tests #19 & #22:
19. SVGPathSegList module: Supports insertItemBefore() (2, 1, 3)
22. SVGPathSegList module: Supports initialize() (2, 1, 3)
Regression range was between the 11/8 & 11/9 nightlies:
http://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=0f92c4e81cf3&tochange=09a2c950dae3
Since the failure is in tests for SVG path APIs, this looks most likely to be a regression from bug 522306.
Reporter | ||
Updated•14 years ago
|
Keywords: regression
Reporter | ||
Comment 1•14 years ago
|
||
If I click the failing tests, it gives this additional information:
> #19: Supports insertItemBefore()
> [green] SVGPathSegList has 'insertItemBefore' property
> [red] Died on test #2: Index or size is negative or greater than the allowed amount
> [red] Expected 3 assertions, but 2 were run
> #22:
> [green] SVGPathSegList has 'initialize' property
> [red] Died on test #2: Index or size is negative or greater than the allowed amount
> [red] Expected 3 assertions, but 2 were run
Reporter | ||
Comment 2•14 years ago
|
||
I get this output in the error console, when I reload the test:
> Warning: Unexpected value L100,200 M1,2 L3,4 parsing d attribute.
> Source File: http://svgtorture.googlecode.com/svn/trunk/automated/test.html
...and...
> Warning: Unexpected value L1,2 parsing d attribute.
> Source File: http://svgtorture.googlecode.com/svn/trunk/automated/test.html
Looks like the first one is from test #19 running this script:
> list.insertItemBefore(makeSeg(100, 200), 0);
and the second one is from test #22 running this script:
> list.initialize(makeSeg(1, 2));
makeSeg is just a helper function in the test that returns "path.createSVGPathSegLinetoAbs(x, y);"
My initial guess is that what's going on is this: whenever we serialize the generated segments to create a "d" attribute, we're not creating a "dummy" move command at the beginning ("m0 0"), and so our "d" attribute ends up rejecting our own generated path specification, because it starts with a non-move-command. (At least in SVG 1.1, path specifications have to start with a move command.)
Reporter | ||
Comment 3•14 years ago
|
||
Here's a mostly-reduced testcase. It runs initialize & insertItemBefore on a path's pathSegList.
Trunk behavior: fails -- the list's number of items is "0" after the initialize call & also after the insertItemBefore call. The final "d" attr is L100,200
Firefox 3.6 behavior: PASS -- the list's number of items is "1" after initialize and "2" after insertItemBefore. The final "d" attr is L100,200 L1,2
Opera matches Firefox 3.6. In all cases, nothing is actually drawn (because the path is invalid at least for the purposes of drawing, without an initial moveto command)
Reporter | ||
Comment 4•14 years ago
|
||
Here's a visual testcase with a little more real-world usefulness. (The previous test and the test on the svg torture site both built paths that wouldn't paint anything, so they were arguably less useful).
This testcase builds up two paths, which both have a description "Move; draw Line". In one case, we add the "line" segment first & then insert the move before it; in the other case, we add the "move" segment first & append the line after it.
The first case fails -- it looks like we completely drop the initially-added line segment (since there's no "move" command in our path's pathSegList at that point and we're perhaps overzealously failing too early).
Reporter | ||
Updated•14 years ago
|
Attachment #489492 -
Attachment description: visual testcase → visual testcase (should show two lines)
Reporter | ||
Updated•14 years ago
|
blocking2.0: --- → ?
Assignee | ||
Updated•14 years ago
|
Assignee: nobody → jwatt
Status: NEW → ASSIGNED
Assignee | ||
Comment 5•14 years ago
|
||
Thanks for filing this, and for all the debugging, dholbert!
Assignee | ||
Comment 6•14 years ago
|
||
The reason this is happening is because whenever the DOM list is changed by script, we call DidChangePathSegList(), which serializes the list, and reparses it. Since the path doesn't start with a valid "M" command, the reparse actually trashes the DOM list.
Fixing bug 609527 in general would fix this bug. Alternatively an easy fix would be to add a guard member to SVGPathSegList which we set/unset around calls to DidChangePathSegList in DOMSVGPathSegList, then in SVGPathData::SetValueFromString just return early if that guard is set.
Assignee | ||
Comment 7•14 years ago
|
||
Another way to fix this would be to stop requiring the first segment in a path to be a moveto segment. That would be my preferred course of action actually. I've sent an email to the SVG WG about that:
http://lists.w3.org/Archives/Public/www-svg/2010Nov/0033.html
blocking2.0: ? → final+
Updated•14 years ago
|
Whiteboard: [softblocker]
Assignee | ||
Comment 8•14 years ago
|
||
Attachment #502774 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Attachment #502774 -
Flags: review?(roc)
Assignee | ||
Comment 9•14 years ago
|
||
Attachment #502774 -
Attachment is obsolete: true
Attachment #502812 -
Flags: review?(jonas)
Attachment #502774 -
Flags: review?(roc)
Attachment #502774 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Attachment #502812 -
Flags: review?(roc)
Assignee | ||
Comment 10•14 years ago
|
||
In terms of perf, note that SetAttrForSVGDOMChange serializes the nsAttrValue that's passed in, so we only avoid half the current perf hit of the serialize-then-parse circle on SVG DOM changes. There are several reasons for doing this including:
1) I think this late in the game we should be keeping the code paths
as similar to what's currently executed (i.e. what SetAttr currently
does in this case) as we can.
2) Even in the long run I think we want to call BeforeSetAttr/
AfterSetAttr as it's unclear that it wont bite us somewhere/
someday to have diverged from SetAttr on that point.
3) It would be nice to keep SetAttrForSVGDOMChange as close to SetAttr
as possible in the long run to make keeping them in sync easier.
4) Perf improvement is not the issue reported in this bug, although
the partial improvement that results from the patch is welcome.
Comment on attachment 502812 [details] [diff] [review]
patch with better test
I think we should move SetAttrForSVGDOMChange to nsGenericElement and call it SetParsedAttr or something like that. We an also share more code here, probably all the code from SetAttr before it calls ParseAttribute can be moved to a helper function and called from SetParsedAttr too.
Assignee | ||
Comment 12•14 years ago
|
||
Attachment #502812 -
Attachment is obsolete: true
Attachment #504570 -
Flags: review?(jonas)
Attachment #502812 -
Flags: review?(roc)
Attachment #502812 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Attachment #504570 -
Flags: review?(roc)
Assignee | ||
Comment 13•14 years ago
|
||
I'm not really in love with the three outparams - refactoring suggestions would be welcome.
Attachment #504570 -
Flags: review?(roc) → review+
Comment on attachment 504570 [details] [diff] [review]
patch with nsGenericElement::SetParsedAttr
> @@ -256,16 +263,25 @@ nsAttrValue::SetTo(const nsAttrValue& aO
> + // XXX under what circumstances do we get here? Are both nsAttrValue
> + // objects guaranteed to be outlived by the nsSVGElement who's member
> + // SVGAnimatedPathSegList they're pointing at?!
I don't know :(
In all other cases the nsAttrValue owns the contained value and we clone the value in this function. Is there any way you could do the same here?
That said, I think it is safe. But we'd need to investigate and document.
One option might be to just copy the string value in this case...
> + // Since [the] SVG path data attribute is never mapped into style, is
> + // this case even necessary (i.e. ever reached)? The comment for the
> + // eAtomArray case seems to suggest the answer is "no".
> + //
> + // XXX should we actually be comparing objects unlike eCSSStyleRule?
Yeah, we should never get here.
You are changing the call order between nsNodeUtils::AttributeWillChange and ParseAttribute. This is probably not safe given that ParseAttribute often change state element state (which is unfortunate but nevertheless the case).
Looks good otherwise.
Attachment #504570 -
Flags: review?(jonas) → review-
Oh, wait, you're not changing that call order. My bad.
Still nervous about the nsAttrValue lifetime issue though.
Comment 16•14 years ago
|
||
> Oh, wait, you're not changing that call order. My bad.
Well... With the new SetParsedAttribute code, are we guaranteed that when AttributeWillChange is called GetAttr still returns the old (pre-change) value?
No.
I guess we need to do more invasive surgery here, so that our SVG code can do some kind of "BeginChangingAttribute"/"EndChangingAttribute" protocol around its modifications to the underlying data.
Probably we should fix this regression in a different way for FF4 and then think about what kind of DOM API restructuring we need here.
Comment 18•14 years ago
|
||
The other option is to not modify objects in-place and instead to clone-on-modification. That's what inline style does, to deal with these issues.
Another expedient option, which jwatt suggested, is to simply serialize the value to a string and always store that in the nsAttrValue. So when the value changes, we can get the string for the old value from the nsAttrValue.
Cloning the SVG data on modification isn't the ultimate solution here because we can be dealing with multi-megabyte path data objects. If someone loops over the path components modifying them (or just builds a really big path by appending to the component list), we'll get O(N^2) behavior due to the clone.
Assignee | ||
Comment 20•14 years ago
|
||
(In reply to comment #14)
>
> > @@ -256,16 +263,25 @@ nsAttrValue::SetTo(const nsAttrValue& aO
> > + // XXX under what circumstances do we get here? Are both nsAttrValue
> > + // objects guaranteed to be outlived by the nsSVGElement who's member
> > + // SVGAnimatedPathSegList they're pointing at?!
>
> One option might be to just copy the string value in this case...
[Replying to this for future reference when I/we will no doubt be looking back on this code when thinking about fixing the general perf issues with modifying SVG element members.]
I don't think we want to just copy the string value and not take over the pointer to the SVGAnimatedPathSegList. I assume we'd get here from under DidChangePathSegList where aOther will be the nsAttrValue passed into SetParsedAttr by DidChangePathSegList, and the nsAttrValue that this SetTo call is occurring on is the nsAttrValue being stored in mAttrsAndChildren. In that case the nsAttrValue in mAttrsAndChildren wouldn't have a reference to the SVGAnimatedPathSegList which would defeat the point of storing SVGAnimatedPathSegList references in nsAttrValues.
Assignee | ||
Comment 21•14 years ago
|
||
(In reply to comment #16)
> With the new SetParsedAttribute code, are we guaranteed that when
> AttributeWillChange is called GetAttr still returns the old (pre-change) value?
Good catch!
(In reply to comment #17)
> No.
Well, "no" if the SVGAnimatedPathSegList has been modified via the SVG DOM since the attribute was last set by the parser/a setAttribute call, but "yes" otherwise since nsSVGElement::ParseAttribute passes the serialized value to the nsAttrValue which stores it using SetMiscAtomOrString.
Comment 22•14 years ago
|
||
> Good catch!
Add tests? ;)
I need to think about comment 19; more on that tomorrow.
Assignee | ||
Comment 23•14 years ago
|
||
In this approach we always store the serialization in the nsAttrValue so that GetAttr will always return the old value under AttributeChanged. Since we're always storing the serialization in nsAttrValue, there's actually no point in it having a reference to the SVGAnimatedPathSegList, so it now doesn't. This neatly sidesteps the nsAttrValue vs element lifetime problem.
Attachment #504570 -
Attachment is obsolete: true
Attachment #506297 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Attachment #506297 -
Flags: review?(roc)
Attachment #506297 -
Flags: review?(roc) → review+
Assignee | ||
Comment 24•14 years ago
|
||
Hmm. There is one problem with the SetAttr changes that we missed, which is that there's a |return NS_OK| line in the code that was moved from SetAttr to SetAttrInternal that is supposed to trigger an early return from SetAttr. As it stands, SetAttr doesn't know when SetAttrInternal returns that NS_OK, so it doesn't know that it too should return early. Jonas, do you have a preferred way to handle that? I'm not sure there's a particularly appropriate error code I can return to signal "return early, but return NS_OK instead of what I'm returning".
Assignee | ||
Comment 25•14 years ago
|
||
Assignee | ||
Comment 26•14 years ago
|
||
I've added the following to the mochitest:
+ d = 'M0,0 L12,34'
+ path.setAttribute('d', d);
+ function check_old_value(e) {
+ is(e.target, path, 'check mutation event is for expected node');
+ is(e.attrName, 'd', 'check mutation event is for expected attribute');
+ is(e.prevValue, d, 'check old attribute value is correctly reported');
+ isnot(e.newValue, d, 'check attribute value has changed');
+ }
+ path.addEventListener('DOMAttrModified', check_old_value, false);
+ list.getItem(1).y = 35;
+ path.removeEventListener('DOMAttrModified', check_old_value, false);
Comment 27•14 years ago
|
||
Also worth adding styling tests, since one obvious way to make mutation events work would be to just pass in the old string... but that won't work for restyles. :(
Assignee | ||
Comment 28•14 years ago
|
||
This is one way to address comment 24. This patch is still working its way thorough Try, but it's looking good so far.
Attachment #506297 -
Attachment is obsolete: true
Attachment #506629 -
Flags: review?(jonas)
Attachment #506297 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Summary: Regression in SVG Torture Tests - "SVGPathSegList module: Supports insertItemBefore()", "SVGPathSegList module: Supports initialize()" → Regression: SVGPathSegList should allow manipulation of invalid paths
Assignee | ||
Comment 29•14 years ago
|
||
Passed Try fine, BTW.
Comment 30•14 years ago
|
||
OK, so the point is that we still have a serialize/reparse cycle, but GetAttr
just gets the string instead of serializing the object, so there are no
ordering issues of the sort I was worried about, right?
Assignee | ||
Comment 31•14 years ago
|
||
(In reply to comment #30)
> OK, so the point is that we still have a serialize/reparse cycle,
Actually we now only have the serialization step because we avoid the reparse by using SetParsedAttr which doesn't call ParseAttribute.
> but GetAttr just gets the string instead of serializing the object,
Right.
> so there are no ordering issues of the sort I was worried about, right?
Right.
Assignee | ||
Comment 32•14 years ago
|
||
Jonas, is this more palatable? I'd like this to not get stuck and miss the Tuesday deadline because the code sharing ugly, so how about we just do this for now and I file a separate bug for sharing code (or not) to be fixed post FF4?
Attachment #508225 -
Flags: review?(jonas)
Comment 33•14 years ago
|
||
Er... were sicking's comments outside this bug somewhere? The sharing code approach seemed way better to me, personally. If Jonas is just swamped, I can review as needed.
Assignee | ||
Comment 34•14 years ago
|
||
Yes, Jonas and I were talking in #developers.
Comment on attachment 508225 [details] [diff] [review]
patch without NS_NOTHING_TO_DO and without code sharing
Yeah, I like this better. Though change SetParsedAttr to not take a namespaceID or prefix as I'd imagine all such attributes are in the null namespace (or does xlink:href need to go through SetParsedAttr?)
And remove the |aPrefix == info.mName->GetPrefix()| check as that will always be true.
We should also make the .style code go through SetParsedAttr rather than duplicating much of it. But that can wait until later.
Attachment #508225 -
Flags: review?(jonas) → review+
Comment 36•14 years ago
|
||
Jonas, can we at least get a bug filed on sharing that mutation listener goop? copy/pasting all that complexity makes me _really_ unhappy... :(
Comment 37•14 years ago
|
||
> We should also make the .style code go through SetParsedAttr
It can't, as this function is currently implemented, because it needs to call AttributeWillChange before the style change happens.
Assignee | ||
Comment 38•14 years ago
|
||
Hopefully this is pretty close to what we agreed. :)
Attachment #506629 -
Attachment is obsolete: true
Attachment #508225 -
Attachment is obsolete: true
Attachment #508680 -
Flags: review?(jonas)
Attachment #506629 -
Flags: review?(jonas)
Assignee | ||
Updated•14 years ago
|
Attachment #508680 -
Attachment is patch: true
Attachment #508680 -
Attachment mime type: application/octet-stream → text/plain
Comment on attachment 508680 [details] [diff] [review]
Patch with outcome of IRC discussion implemented
Sold! Though maybe simply call the function MaybeCheckSameAttrVal or just CheckSameAttrVal. The documentation can mention that as a perf optimization we don't actually check if we aren't notifying and don't have mutation listeners.
Attachment #508680 -
Flags: review?(jonas) → review+
Assignee | ||
Comment 40•14 years ago
|
||
Status: ASSIGNED → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla2.0b11
Comment 41•14 years ago
|
||
Fwiw, the documentation for MaybeCheckSameAttrVal is wrong. Needs s/different to the current value/the same as the current value/.
Comment 42•14 years ago
|
||
Verified fixed in Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b12pre) Gecko/20110204 Firefox/4.0b12pre
Status: RESOLVED → VERIFIED
Whiteboard: [softblocker] → [softblocker][fx4-fixed-bugday]
You need to log in
before you can comment on or make changes to this bug.
Description
•