blob: 74cd5c4124c93e30eacf561bd74d0268119e3c7a [file] [log] [blame]
/*
* Copyright (C) 2021-2025 Apple Inc. All rights reserved.
* Copyright (C) 2023, 2024 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ReferencedSVGResources.h"
#include "LegacyRenderSVGResourceClipper.h"
#include "PathOperation.h"
#include "RenderLayer.h"
#include "RenderObjectInlines.h"
#include "RenderSVGPath.h"
#include "RenderStyle.h"
#include "SVGClipPathElement.h"
#include "SVGElementTypeHelpers.h"
#include "SVGFilterElement.h"
#include "SVGMarkerElement.h"
#include "SVGMaskElement.h"
#include "SVGResourceElementClient.h"
#include "Settings.h"
#include "StyleFilterReference.h"
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
class CSSSVGResourceElementClient final : public SVGResourceElementClient {
WTF_MAKE_TZONE_ALLOCATED(CSSSVGResourceElementClient);
WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(CSSSVGResourceElementClient);
public:
CSSSVGResourceElementClient(RenderElement& clientRenderer)
: m_clientRenderer(clientRenderer)
{
}
void resourceChanged(SVGElement&) final;
const RenderElement& renderer() const final { return m_clientRenderer.get(); }
private:
const CheckedRef<RenderElement> m_clientRenderer;
};
WTF_MAKE_TZONE_ALLOCATED_IMPL(CSSSVGResourceElementClient);
void CSSSVGResourceElementClient::resourceChanged(SVGElement& element)
{
if (m_clientRenderer->renderTreeBeingDestroyed())
return;
if (!m_clientRenderer->document().settings().layerBasedSVGEngineEnabled()) {
m_clientRenderer->repaint();
return;
}
if (m_clientRenderer->needsLayout())
return;
// Special case for markers. Markers can be attached to RenderSVGPath object. Marker positions are computed
// once during layout, or if the shape itself changes. Here we manually update the marker positions without
// requiring a relayout. Instead we can simply repaint the path - via the updateLayerPosition() logic, properly
// repainting the old repaint boundaries and the new ones (after the marker change).
if (auto* pathClientRenderer = dynamicDowncast<RenderSVGPath>(m_clientRenderer.get()); pathClientRenderer && is<SVGMarkerElement>(element))
pathClientRenderer->updateMarkerPositions();
m_clientRenderer->repaintOldAndNewPositionsForSVGRenderer();
}
WTF_MAKE_TZONE_ALLOCATED_IMPL(ReferencedSVGResources);
ReferencedSVGResources::ReferencedSVGResources(RenderElement& renderer)
: m_renderer(renderer)
{
}
ReferencedSVGResources::~ReferencedSVGResources()
{
Ref treeScope = m_renderer->treeScopeForSVGReferences();
for (auto& targetID : copyToVector(m_elementClients.keys()))
removeClientForTarget(treeScope, targetID);
}
void ReferencedSVGResources::addClientForTarget(SVGElement& targetElement, const AtomString& targetID)
{
m_elementClients.ensure(targetID, [&] {
auto client = makeUnique<CSSSVGResourceElementClient>(m_renderer);
targetElement.addReferencingCSSClient(*client);
return client;
});
}
void ReferencedSVGResources::removeClientForTarget(TreeScope& treeScope, const AtomString& targetID)
{
auto client = m_elementClients.take(targetID);
if (RefPtr targetElement = dynamicDowncast<SVGElement>(treeScope.getElementById(targetID)))
targetElement->removeReferencingCSSClient(*client);
}
ReferencedSVGResources::SVGElementIdentifierAndTagPairs ReferencedSVGResources::referencedSVGResourceIDs(const RenderStyle& style, const Document& document)
{
SVGElementIdentifierAndTagPairs referencedResources;
WTF::switchOn(style.clipPath(),
[&](const Style::ReferencePath& clipPath) {
if (!clipPath.fragment().isEmpty())
referencedResources.append({ clipPath.fragment(), { SVGNames::clipPathTag } });
},
[](const auto&) { }
);
for (auto& value : style.filter()) {
WTF::switchOn(value,
[&](const Style::FilterReference& filterReference) {
if (!filterReference.cachedFragment.isEmpty())
referencedResources.append({ filterReference.cachedFragment, { SVGNames::filterTag } });
},
[]<CSSValueID C, typename T>(const FunctionNotation<C, T>&) { }
);
}
if (!document.settings().layerBasedSVGEngineEnabled())
return referencedResources;
if (style.hasPositionedMask()) {
// FIXME: We should support all the values in the CSS mask property, but for now just use the first mask-image if it's a reference.
if (RefPtr maskImage = style.maskLayers().usedFirst().image().tryStyleImage()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(maskImage->url(), document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::maskTag } });
}
}
if (style.hasMarkers()) {
if (auto markerStartResource = style.markerStart(); !markerStartResource.isNone()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(markerStartResource, document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::markerTag } });
}
if (auto markerMidResource = style.markerMid(); !markerMidResource.isNone()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(markerMidResource, document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::markerTag } });
}
if (auto markerEndResource = style.markerEnd(); !markerEndResource.isNone()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(markerEndResource, document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::markerTag } });
}
}
if (auto fillURL = style.fill().tryAnyURL()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(*fillURL, document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::linearGradientTag, SVGNames::radialGradientTag, SVGNames::patternTag } });
}
if (auto strokeURL = style.stroke().tryAnyURL()) {
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(*strokeURL, document);
if (!resourceID.isEmpty())
referencedResources.append({ resourceID, { SVGNames::linearGradientTag, SVGNames::radialGradientTag, SVGNames::patternTag } });
}
return referencedResources;
}
void ReferencedSVGResources::updateReferencedResources(TreeScope& treeScope, const ReferencedSVGResources::SVGElementIdentifierAndTagPairs& referencedResources)
{
HashSet<AtomString> oldKeys;
for (auto& key : m_elementClients.keys())
oldKeys.add(key);
for (auto& [targetID, tagNames] : referencedResources) {
RefPtr element = elementForResourceIDs(treeScope, targetID, tagNames);
if (!element)
continue;
addClientForTarget(*element, targetID);
oldKeys.remove(targetID);
}
for (auto& targetID : oldKeys)
removeClientForTarget(treeScope, targetID);
}
// SVG code uses getRenderSVGResourceById<>, but that works in terms of renderers. We need to find resources
// before the render tree is fully constructed, so this works on Elements.
RefPtr<SVGElement> ReferencedSVGResources::elementForResourceID(TreeScope& treeScope, const AtomString& resourceID, const SVGQualifiedName& tagName)
{
RefPtr element = dynamicDowncast<SVGElement>(treeScope.getElementById(resourceID));
if (!element || !element->hasTagName(tagName))
return nullptr;
return element;
}
RefPtr<SVGElement> ReferencedSVGResources::elementForResourceIDs(TreeScope& treeScope, const AtomString& resourceID, const SVGQualifiedNames& tagNames)
{
RefPtr element = dynamicDowncast<SVGElement>(treeScope.getElementById(resourceID));
if (!element)
return nullptr;
for (const auto& tagName : tagNames) {
if (element->hasTagName(tagName))
return element;
}
return nullptr;
}
RefPtr<SVGClipPathElement> ReferencedSVGResources::referencedClipPathElement(TreeScope& treeScope, const Style::ReferencePath& clipPath)
{
if (clipPath.fragment().isEmpty())
return nullptr;
return downcast<SVGClipPathElement>(elementForResourceID(treeScope, clipPath.fragment(), SVGNames::clipPathTag));
}
RefPtr<SVGMarkerElement> ReferencedSVGResources::referencedMarkerElement(TreeScope& treeScope, const Style::URL& markerResource)
{
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(markerResource, treeScope.protectedDocumentScope());
if (resourceID.isEmpty())
return nullptr;
return downcast<SVGMarkerElement>(elementForResourceID(treeScope, resourceID, SVGNames::markerTag));
}
RefPtr<SVGMaskElement> ReferencedSVGResources::referencedMaskElement(TreeScope& treeScope, const StyleImage& maskImage)
{
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(maskImage.url(), treeScope.protectedDocumentScope());
if (resourceID.isEmpty())
return nullptr;
return referencedMaskElement(treeScope, resourceID);
}
RefPtr<SVGMaskElement> ReferencedSVGResources::referencedMaskElement(TreeScope& treeScope, const AtomString& resourceID)
{
return downcast<SVGMaskElement>(elementForResourceID(treeScope, resourceID, SVGNames::maskTag));
}
RefPtr<SVGElement> ReferencedSVGResources::referencedPaintServerElement(TreeScope& treeScope, const Style::URL& uri)
{
auto resourceID = SVGURIReference::fragmentIdentifierFromIRIString(uri, treeScope.protectedDocumentScope());
if (resourceID.isEmpty())
return nullptr;
return elementForResourceIDs(treeScope, resourceID, { SVGNames::linearGradientTag, SVGNames::radialGradientTag, SVGNames::patternTag });
}
RefPtr<SVGFilterElement> ReferencedSVGResources::referencedFilterElement(TreeScope& treeScope, const Style::FilterReference& filterReference)
{
if (filterReference.cachedFragment.isEmpty())
return nullptr;
return downcast<SVGFilterElement>(elementForResourceID(treeScope, filterReference.cachedFragment, SVGNames::filterTag));
}
LegacyRenderSVGResourceClipper* ReferencedSVGResources::referencedClipperRenderer(TreeScope& treeScope, const Style::ReferencePath& clipPath)
{
if (clipPath.fragment().isEmpty())
return nullptr;
// For some reason, SVG stores a cache of id -> renderer, rather than just using getElementById() and renderer().
return getRenderSVGResourceById<LegacyRenderSVGResourceClipper>(treeScope, clipPath.fragment());
}
LegacyRenderSVGResourceContainer* ReferencedSVGResources::referencedRenderResource(TreeScope& treeScope, const AtomString& fragment)
{
if (fragment.isEmpty())
return nullptr;
return getRenderSVGResourceContainerById(treeScope, fragment);
}
} // namespace WebCore