Created attachment 246854 [details]
Open the attached test case. It has different lines which are drawn using <line> and <path> elements. All the lines are clipped to a circle <clipPath> which is defined later.
Results: Only the horizontal and vertical lines which are defined before defining the clipPath elements are clipped completely. If the page is repainted (through zooming for example), these missing lines are drawn as expected.
The reason for this bug is the following.
-- RenderSVGResourceClipper::resourceBoundingBox() returns the clipping box of the <clipPath> element for the referencing element. (the <line> or the <path> in the test case)
-- If the <clipPath> is defined after it is referenced, RenderSVGResourceClipper::resourceBoundingBox() will see that selfNeedsLayout() is true so it will return object.objectBoundingBox().
-- For the case of horizontal and vertical line, the object.objectBoundingBox() height and width is zero. This means the clipping rectangle is just empty rectangle in this case.
-- Later when the base class RenderSVGResourceContainer::layout() runs the layout for the <clipPath>, it figures out it might need to notify its clients.
-- But it checks (everHadLayout() && selfNeedsLayout()) before doing so.
-- Because it is the first time we run the layout for this <clipPath>, everHadLayout() returns false and end up not calling RenderSVGRoot::addResourceForClientInvalidation()
-- Therefore the horizontal and vertical lines are completely clipped since their last clipping boxes were empty rectangles.
One interesting thing to notice is if the line is neither horizontal nor vertical, its bonding box is not an empty rectangle. In this case, no clipping is applied to this line not even the <clipPath>. The same thing can happen if more than a line is put a group such that their bounding box is not empty rectangle.
The fix can be the following:
-- We can keep track of the <clipPath> which is referenced before it is defined by adding the RendererObject to the m_clipper in RenderSVGResourceClipper::resourceBoundingBox().
-- In RenderSVGResourceClipper::applyClippingToContext() we need to ensure the RendererElement is in m_clipper if it does not exist.
-- In RenderSVGResourceContainer::layout() we need to use a new virtual function: selfNeedsClientInvalidation() which controls when we need to add the <clipPath> object for clientInvalidation.
-- In the base class we implement selfNeedsClientInvalidation() as it was before: (everHadLayout() && selfNeedsLayout())
-- In the derived class we implement selfNeedsClientInvalidation() the same as the base but we add the clause: ((m_clipper.size() && selfNeedsLayout())
Created attachment 246855 [details]
Created attachment 246862 [details]
Comment on attachment 246862 [details]
Clearing flags on attachment: 246862
Committed r180643: <http://trac.webkit.org/changeset/180643>
All reviewed patches have been landed. Closing bug.
Comment on attachment 246862 [details]
View in context: https://bugs.webkit.org/attachment.cgi?id=246862&action=review
> +ClipperData* RenderSVGResourceClipper::addRendererToClipper(const RenderObject& object)
The pointer this returns is always non-null. This function should return a reference instead of a pointer.
> + if (!m_clipper.contains(&object))
> + m_clipper.set(&object, std::make_unique<ClipperData>());
> + return m_clipper.get(&object);
This code does double hash table lookups. You just moved it, but the performance problem can be fixed, like this:
auto& slot = m_clipper.add(&object, nullptr).iterator->value;
slot = std::make_unique<ClipperData>();
That will only do a single hash table lookup. In the future we might invent even cleaner ways to write this.
We could also consider making this a HashMap<const RenderObject*, ClipperData> instead. If we did that, then the code would be even simpler:
return m_clipper.add(&object, ClipperData()).iterator->value;
The map would be larger, because each slot would have an entire ClipperData object. And rehashing would be slower, but we would avoid the overhead of an additional memory block for every slot that is in use.
I logged https://bugs.webkit.org/show_bug.cgi?id=142032 to track Darin's comments.