Commit e7d7225c authored by fs's avatar fs Committed by Commit bot

Make fragment-only URLs always be document-local references

This implements the 'local url' handling per:

  https://drafts.csswg.org/css-values/#local-urls

which is also referenced from:

  https://svgwg.org/svg2-draft/linking.html#linkRefAttrs

Most of the logic is handled by a new helper class named
SVGURLReferenceResolver, which keeps state, resolves the URL
and extracts the fragment identifier as needed.

BUG=470608

Review-Url: https://codereview.chromium.org/2174833002
Cr-Commit-Position: refs/heads/master@{#407299}
parent 5c7ce5d4
<!DOCTYPE html>
<div style="width: 100px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://www.example.com/">
<style>
#target {
width: 100px;
height: 100px;
background-color: red;
filter: url(#filter);
}
</style>
<div id="target"></div>
<svg>
<filter id="filter" x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB">
<feFlood flood-color="green"/>
</filter>
</svg>
<!DOCTYPE html>
<div style="width: 100px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://www.example.com/">
<style>
#target {
width: 100px;
height: 100px;
border-right: 100px solid red;
background-color: green;
-webkit-clip-path: url(#clip);
clip-path: url(#clip);
}
</style>
<div id="target"></div>
<svg>
<clipPath id="clip">
<rect width="100" height="100"/>
</filter>
</svg>
<!DOCTYPE html>
<div style="width: 200px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://127.0.0.1:8000/svg/resources/red100x100.svg">
<svg>
<rect id="rect" width="100" height="100" fill="green"/>
<use xlink:href="#rect" x="100"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<rect id="rect" width="100" height="100" fill="red"/>
</svg>
layer at (0,0) size 800x600 clip at (0,0) size 785x600 scrollHeight 604
LayoutView at (0,0) size 800x600
layer at (0,0) size 785x600
LayoutBlockFlow {html} at (0,0) size 785x600
LayoutInline {base} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutSVGRoot {svg} at (0,0) size 100x100
LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
LayoutSVGResourceLinearGradient {linearGradient} [id="orange_red"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,0)]
LayoutSVGGradientStop {stop} [offset=0.00] [color=#FF0000]
LayoutSVGGradientStop {stop} [offset=1.00] [color=#FF0000]
LayoutSVGRect {rect} at (0,0) size 100x100 [x=0.00] [y=0.00] [width=100.00] [height=100.00]
LayoutText {#text} at (0,0) size 0x0
layer at (0,0) size 800x600 clip at (0,0) size 785x600 scrollHeight 604
LayoutView at (0,0) size 800x600
layer at (0,0) size 785x600
LayoutBlockFlow {html} at (0,0) size 785x600
LayoutInline {base} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutSVGRoot {svg} at (0,0) size 100x100
LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
LayoutSVGResourceLinearGradient {linearGradient} [id="orange_red"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,0)]
LayoutSVGGradientStop {stop} [offset=0.00] [color=#FF0000]
LayoutSVGGradientStop {stop} [offset=1.00] [color=#FF0000]
LayoutSVGRect {rect} at (0,0) size 100x100 [x=0.00] [y=0.00] [width=100.00] [height=100.00]
LayoutText {#text} at (0,0) size 0x0
layer at (0,0) size 800x600 clip at (0,0) size 785x600 scrollHeight 603
LayoutView at (0,0) size 800x600
layer at (0,0) size 785x600
LayoutBlockFlow {html} at (0,0) size 785x600
LayoutInline {base} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutText {#text} at (0,0) size 0x0
LayoutSVGRoot {svg} at (0,0) size 100x100
LayoutSVGHiddenContainer {defs} at (0,0) size 0x0
LayoutSVGResourceLinearGradient {linearGradient} [id="orange_red"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,0)]
LayoutSVGGradientStop {stop} [offset=0.00] [color=#FF0000]
LayoutSVGGradientStop {stop} [offset=1.00] [color=#FF0000]
LayoutSVGRect {rect} at (0,0) size 100x100 [x=0.00] [y=0.00] [width=100.00] [height=100.00]
LayoutText {#text} at (0,0) size 0x0
<!DOCTYPE html>
<div style="width: 200px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://www.example.com/">
<svg>
<script>
var svgRoot = document.querySelector('svg');
svgRoot.pauseAnimations();
svgRoot.setCurrentTime(1);
</script>
<defs>
<path id="path" d="M100,0z"/>
</defs>
<rect width="100" height="100" fill="green">
<animateMotion dur="0.01" fill="freeze">
<mpath xlink:href="#path"/>
</animateMotion>
</rect>
<rect id="rect" width="100" height="100" fill="red"/>
<set attributeName="fill" xlink:href="#rect" to="green" dur="indefinite"/>
</svg>
<!DOCTYPE html> <!DOCTYPE html>
<svg> <svg>
<rect id="rect" width="100" height="100" fill="green"/> <rect width="100" height="100" fill="green"/>
<rect x="150" width="100" height="100" fill="green"/>
</svg> </svg>
<?xml version="1.0" standalone="no"?>
<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%">
<base href="http://www.example.com"/>
<!-- There should be no red -->
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="orange_red" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:red;"/>
<stop offset="100%" style="stop-color:red;"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="100" height="100" style="fill: url(#orange_red)"/>
</svg>
</html>
<!DOCTYPE html>
<div style="width: 100px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<iframe srcdoc="
<!DOCTYPE html>
<style>body { margin: 0; }</style>
<svg xmlns='http://www.w3.org/2000/svg'>
<linearGradient id='gradient'>
<stop stop-color='green'/>
</linearGradient>
<rect width='100' height='100' fill='url(#gradient) red'/>
</svg>"
style="margin: 0; border: none" scrolling="no">
</iframe>
<!DOCTYPE html>
<div style="width: 400px; height: 300px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://www.example.com/">
<svg width="400" height="300">
<linearGradient id="paint">
<stop stop-color="green"/>
</linearGradient>
<linearGradient id="inheritedPaint" xlink:href="#paint"/>
<rect id="rect" width="100" height="100" fill="url(#paint) red"/>
<rect x="125" y="25" width="50" height="50" stroke="url(#paint) red" stroke-width="50"/>
<use x="200" xlink:href="#rect"/>
<rect x="300" width="100" height="100" fill="url(#inheritedPaint) red"/>
<filter id="filter" x="0" y="0" width="1" height="1" color-interpolation-filters="sRGB">
<feImage xlink:href="#rect"/>
</filter>
<rect y="100" width="100" height="100" fill="red" filter="url(#filter)"/>
<clipPath id="clip">
<rect y="50" width="100" height="50"/>
</clipPath>
<rect x="100" y="100" width="100" height="50" fill="green"/>
<g clip-path="url(#clip)" transform="translate(100, 100)">
<rect width="100" height="50" fill="red"/>
<rect y="50" width="100" height="50" fill="green"/>
</g>
<mask id="mask">
<rect y="50" width="100" height="50" fill="white"/>
</mask>
<rect y="100" x="200" width="100" height="50" fill="green"/>
<g mask="url(#mask)" transform="translate(200, 100)">
<rect width="100" height="50" fill="red"/>
<rect y="50" width="100" height="50" fill="green"/>
</g>
<pattern id="pattern" width="100" height="100" patternUnits="userSpaceOnUse">
<rect width="100" height="100" fill="green"/>
</pattern>
<pattern id="inheritedPattern" xlink:href="#pattern"/>
<rect x="300" y="100" width="100" height="100" fill="url(#inheritedPattern) red"/>
<marker id="marker" refY="0.5">
<rect width="1" height="1" fill="green"/>
</marker>
<polyline points="0,250 100,250 200,250" stroke="red" stroke-width="100"
marker-start="url(#marker)" marker-mid="url(#marker)" marker-end="url(#marker)"/>
<radialGradient id="radial">
<stop stop-color="green"/>
</radialGradient>
<radialGradient id="inheritedRadial" xlink:href="#radial"/>
<rect x="300" y="200" width="100" height="100" fill="url(#inheritedRadial) red"/>
</svg>
<!DOCTYPE html>
<div style="width: 100px; height: 100px; background-color: green"></div>
<!DOCTYPE html>
<base href="http://www.example.com/">
<script src="../../resources/ahem.js"></script>
<svg>
<path id="path" d="M0,80h100"/>
<text font-size="100" font-family="Ahem" fill="green"><textPath xlink:href="#path">X</textPath></textPath>
</svg>
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "core/css/resolver/StyleResolverState.h" #include "core/css/resolver/StyleResolverState.h"
#include "core/frame/UseCounter.h" #include "core/frame/UseCounter.h"
#include "core/layout/svg/ReferenceFilterBuilder.h" #include "core/layout/svg/ReferenceFilterBuilder.h"
#include "core/svg/SVGURIReference.h"
namespace blink { namespace blink {
...@@ -130,10 +131,9 @@ FilterOperations FilterOperationResolver::createFilterOperations(StyleResolverSt ...@@ -130,10 +131,9 @@ FilterOperations FilterOperationResolver::createFilterOperations(StyleResolverSt
countFilterUse(FilterOperation::REFERENCE, state.document()); countFilterUse(FilterOperation::REFERENCE, state.document());
const CSSURIValue& urlValue = toCSSURIValue(*currValue); const CSSURIValue& urlValue = toCSSURIValue(*currValue);
KURL url = state.document().completeURL(urlValue.url()); SVGURLReferenceResolver resolver(urlValue.value(), state.document());
ReferenceFilterOperation* operation = ReferenceFilterOperation::create(urlValue.value(), resolver.fragmentIdentifier());
ReferenceFilterOperation* operation = ReferenceFilterOperation::create(urlValue.url(), AtomicString(url.fragmentIdentifier())); if (!resolver.isLocal()) {
if (!equalIgnoringFragmentIdentifier(url, state.document().url())) {
if (!urlValue.loadRequested()) if (!urlValue.loadRequested())
state.elementStyleResources().addPendingSVGDocument(operation, &urlValue); state.elementStyleResources().addPendingSVGDocument(operation, &urlValue);
else if (urlValue.cachedDocument()) else if (urlValue.cachedDocument())
......
...@@ -123,10 +123,9 @@ PassRefPtr<ClipPathOperation> StyleBuilderConverter::convertClipPath(StyleResolv ...@@ -123,10 +123,9 @@ PassRefPtr<ClipPathOperation> StyleBuilderConverter::convertClipPath(StyleResolv
if (value.isBasicShapeValue()) if (value.isBasicShapeValue())
return ShapeClipPathOperation::create(basicShapeForValue(state, value)); return ShapeClipPathOperation::create(basicShapeForValue(state, value));
if (value.isURIValue()) { if (value.isURIValue()) {
String cssURLValue = toCSSURIValue(value).value(); SVGURLReferenceResolver resolver(toCSSURIValue(value).value(), state.document());
KURL url = state.document().completeURL(cssURLValue);
// TODO(fs): Doesn't work with forward or external SVG references (crbug.com/391604, crbug.com/109212, ...) // TODO(fs): Doesn't work with forward or external SVG references (crbug.com/391604, crbug.com/109212, ...)
return ReferenceClipPathOperation::create(cssURLValue, AtomicString(url.fragmentIdentifier())); return ReferenceClipPathOperation::create(toCSSURIValue(value).value(), resolver.fragmentIdentifier());
} }
DCHECK(value.isPrimitiveValue() && toCSSPrimitiveValue(value).getValueID() == CSSValueNone); DCHECK(value.isPrimitiveValue() && toCSSPrimitiveValue(value).getValueID() == CSSValueNone);
return nullptr; return nullptr;
......
...@@ -56,24 +56,47 @@ KURL SVGURIReference::legacyHrefURL(const Document& document) const ...@@ -56,24 +56,47 @@ KURL SVGURIReference::legacyHrefURL(const Document& document) const
return document.completeURL(stripLeadingAndTrailingHTMLSpaces(hrefString())); return document.completeURL(stripLeadingAndTrailingHTMLSpaces(hrefString()));
} }
AtomicString SVGURIReference::fragmentIdentifierFromIRIString(const String& urlString, const TreeScope& treeScope) SVGURLReferenceResolver::SVGURLReferenceResolver(const String& urlString, const Document& document)
: m_relativeUrl(urlString)
, m_document(&document)
, m_isLocal(urlString.startsWith('#'))
{
}
KURL SVGURLReferenceResolver::absoluteUrl() const
{
if (m_absoluteUrl.isNull())
m_absoluteUrl = m_document->completeURL(m_relativeUrl);
return m_absoluteUrl;
}
bool SVGURLReferenceResolver::isLocal() const
{ {
const Document& document = treeScope.document(); return m_isLocal || equalIgnoringFragmentIdentifier(absoluteUrl(), m_document->url());
}
AtomicString SVGURLReferenceResolver::fragmentIdentifier() const
{
// If this is a "fragment-only" URL, then the reference is always local, so
// just return what's after the '#' as the fragment.
if (m_isLocal)
return AtomicString(m_relativeUrl.substring(1));
return AtomicString(absoluteUrl().fragmentIdentifier());
}
KURL url = document.completeURL(urlString); AtomicString SVGURIReference::fragmentIdentifierFromIRIString(const String& urlString, const TreeScope& treeScope)
if (!url.hasFragmentIdentifier() || !equalIgnoringFragmentIdentifier(url, document.url())) {
SVGURLReferenceResolver resolver(urlString, treeScope.document());
if (!resolver.isLocal())
return emptyAtom; return emptyAtom;
return AtomicString(url.fragmentIdentifier()); return resolver.fragmentIdentifier();
} }
Element* SVGURIReference::targetElementFromIRIString(const String& urlString, const TreeScope& treeScope, AtomicString* fragmentIdentifier) Element* SVGURIReference::targetElementFromIRIString(const String& urlString, const TreeScope& treeScope, AtomicString* fragmentIdentifier)
{ {
const Document& document = treeScope.document(); AtomicString id = fragmentIdentifierFromIRIString(urlString, treeScope);
if (id.isEmpty())
KURL url = document.completeURL(urlString);
if (!url.hasFragmentIdentifier() || !equalIgnoringFragmentIdentifier(url, document.url()))
return nullptr; return nullptr;
AtomicString id(url.fragmentIdentifier());
if (fragmentIdentifier) if (fragmentIdentifier)
*fragmentIdentifier = id; *fragmentIdentifier = id;
return treeScope.getElementById(id); return treeScope.getElementById(id);
......
...@@ -48,18 +48,6 @@ public: ...@@ -48,18 +48,6 @@ public:
static AtomicString fragmentIdentifierFromIRIString(const String&, const TreeScope&); static AtomicString fragmentIdentifierFromIRIString(const String&, const TreeScope&);
static Element* targetElementFromIRIString(const String&, const TreeScope&, AtomicString* = nullptr); static Element* targetElementFromIRIString(const String&, const TreeScope&, AtomicString* = nullptr);
static inline bool isExternalURIReference(const String& uri, const Document& document)
{
// Fragment-only URIs are always internal if the baseURL is same as the document URL.
// This is common case, so check that first to avoid resolving URL (which is relatively expensive). See crbug.com/557979
if (document.baseURL() == document.url() && uri.startsWith('#'))
return false;
// If the URI matches our documents URL, we're dealing with a local reference.
KURL url = document.completeURL(uri);
return !equalIgnoringFragmentIdentifier(url, document.url());
}
const String& hrefString() const { return m_href->currentValue()->value(); } const String& hrefString() const { return m_href->currentValue()->value(); }
// JS API // JS API
...@@ -74,6 +62,24 @@ private: ...@@ -74,6 +62,24 @@ private:
Member<SVGAnimatedHref> m_href; Member<SVGAnimatedHref> m_href;
}; };
// Helper class used to resolve fragment references. Handles the 'local url
// flag' per https://drafts.csswg.org/css-values/#local-urls .
class SVGURLReferenceResolver {
STACK_ALLOCATED();
public:
SVGURLReferenceResolver(const String& urlString, const Document&);
bool isLocal() const;
KURL absoluteUrl() const;
AtomicString fragmentIdentifier() const;
private:
const String& m_relativeUrl;
Member<const Document> m_document;
mutable KURL m_absoluteUrl;
bool m_isLocal;
};
} // namespace blink } // namespace blink
#endif // SVGURIReference_h #endif // SVGURIReference_h
...@@ -184,16 +184,21 @@ void SVGUseElement::collectStyleForPresentationAttribute(const QualifiedName& na ...@@ -184,16 +184,21 @@ void SVGUseElement::collectStyleForPresentationAttribute(const QualifiedName& na
SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style); SVGGraphicsElement::collectStyleForPresentationAttribute(name, value, style);
} }
bool SVGUseElement::isStructurallyExternal() const
{
return !m_elementIdentifierIsLocal;
}
void SVGUseElement::updateTargetReference() void SVGUseElement::updateTargetReference()
{ {
KURL resolvedUrl = document().completeURL(hrefString()); SVGURLReferenceResolver resolver(hrefString(), document());
m_elementIdentifier = AtomicString(resolvedUrl.fragmentIdentifier()); m_elementIdentifier = resolver.fragmentIdentifier();
m_elementIdentifierIsLocal = resolvedUrl.isNull() m_elementIdentifierIsLocal = resolver.isLocal();
|| equalIgnoringFragmentIdentifier(resolvedUrl, document().url());
if (m_elementIdentifierIsLocal) { if (m_elementIdentifierIsLocal) {
setDocumentResource(nullptr); setDocumentResource(nullptr);
return; return;
} }
KURL resolvedUrl = resolver.absoluteUrl();
if (m_elementIdentifier.isEmpty() if (m_elementIdentifier.isEmpty()
|| (m_resource && equalIgnoringFragmentIdentifier(resolvedUrl, m_resource->url()))) || (m_resource && equalIgnoringFragmentIdentifier(resolvedUrl, m_resource->url())))
return; return;
......
...@@ -73,7 +73,7 @@ private: ...@@ -73,7 +73,7 @@ private:
void collectStyleForPresentationAttribute(const QualifiedName&, const AtomicString&, MutableStylePropertySet*) override; void collectStyleForPresentationAttribute(const QualifiedName&, const AtomicString&, MutableStylePropertySet*) override;
bool isPresentationAttributeWithSVGDOM(const QualifiedName&) const override; bool isPresentationAttributeWithSVGDOM(const QualifiedName&) const override;
bool isStructurallyExternal() const override { return !hrefString().isNull() && isExternalURIReference(hrefString(), document()); } bool isStructurallyExternal() const override;
InsertionNotificationRequest insertedInto(ContainerNode*) override; InsertionNotificationRequest insertedInto(ContainerNode*) override;
void removedFrom(ContainerNode*) override; void removedFrom(ContainerNode*) override;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment