Bug 141776 - Horizontal and vertical lines are clipped completely if clip-path is included in the tag but the referenced element is defined later
Summary: Horizontal and vertical lines are clipped completely if clip-path is included...
Alias: None
Product: WebKit
Classification: Unclassified
Component: SVG (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Said Abou-Hallawa
Depends on:
Reported: 2015-02-18 16:04 PST by Said Abou-Hallawa
Modified: 2015-02-25 17:46 PST (History)
6 users (show)

See Also:

test case (846 bytes, image/svg+xml)
2015-02-18 16:04 PST, Said Abou-Hallawa
no flags Details
test case (1.30 KB, image/svg+xml)
2015-02-18 16:05 PST, Said Abou-Hallawa
no flags Details
Patch (12.04 KB, patch)
2015-02-18 17:31 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Said Abou-Hallawa 2015-02-18 16:04:27 PST
Created attachment 246854 [details]
test case

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())
Comment 1 Said Abou-Hallawa 2015-02-18 16:05:32 PST
Created attachment 246855 [details]
test case
Comment 2 Said Abou-Hallawa 2015-02-18 17:31:23 PST
Created attachment 246862 [details]
Comment 3 Said Abou-Hallawa 2015-02-25 15:05:35 PST
Comment on attachment 246862 [details]

Clearing flags on attachment: 246862

Committed r180643: <http://trac.webkit.org/changeset/180643>
Comment 4 Said Abou-Hallawa 2015-02-25 15:05:38 PST
All reviewed patches have been landed.  Closing bug.
Comment 5 Darin Adler 2015-02-25 15:22:57 PST
Comment on attachment 246862 [details]

View in context: https://bugs.webkit.org/attachment.cgi?id=246862&action=review

> Source/WebCore/rendering/svg/RenderSVGResourceClipper.cpp:260
> +ClipperData* RenderSVGResourceClipper::addRendererToClipper(const RenderObject& object)

The pointer this returns is always non-null. This function should return a reference instead of a pointer.

> Source/WebCore/rendering/svg/RenderSVGResourceClipper.cpp:264
> +    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;
    if (!slot)
        slot = std::make_unique<ClipperData>();
    return slot.get();

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.
Comment 6 Said Abou-Hallawa 2015-02-25 17:46:53 PST
I logged https://bugs.webkit.org/show_bug.cgi?id=142032 to track Darin's comments.