1/*
2 * Copyright (C) 2006 Rob Buis <buis@kde.org>
3 *           (C) 2008 Nikolas Zimmermann <zimmermann@kde.org>
4 * Copyright (C) 2008 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "CSSCursorImageValue.h"
24
25#include "CSSImageValue.h"
26#include "CachedImage.h"
27#include "CachedResourceLoader.h"
28#include "StyleCachedImage.h"
29#include "StyleImage.h"
30#include "StylePendingImage.h"
31#include "SVGCursorElement.h"
32#include "SVGLengthContext.h"
33#include "SVGNames.h"
34#include "SVGURIReference.h"
35#include "TreeScope.h"
36#include <wtf/MathExtras.h>
37#include <wtf/text/StringBuilder.h>
38#include <wtf/text/WTFString.h>
39
40#if ENABLE(CSS_IMAGE_SET)
41#include "CSSImageSetValue.h"
42#include "StyleCachedImageSet.h"
43#endif
44
45namespace WebCore {
46
47static inline SVGCursorElement* resourceReferencedByCursorElement(const String& url, Document& document)
48{
49    Element* element = SVGURIReference::targetElementFromIRIString(url, document);
50    if (element && isSVGCursorElement(element))
51        return toSVGCursorElement(element);
52
53    return 0;
54}
55
56CSSCursorImageValue::CSSCursorImageValue(PassRef<CSSValue> imageValue, bool hasHotSpot, const IntPoint& hotSpot)
57    : CSSValue(CursorImageClass)
58    , m_imageValue(WTF::move(imageValue))
59    , m_hasHotSpot(hasHotSpot)
60    , m_hotSpot(hotSpot)
61    , m_accessedImage(false)
62{
63}
64
65inline void CSSCursorImageValue::detachPendingImage()
66{
67    if (m_image && m_image->isPendingImage())
68        toStylePendingImage(*m_image).detachFromCSSValue();
69}
70
71CSSCursorImageValue::~CSSCursorImageValue()
72{
73    detachPendingImage();
74
75    if (!isSVGCursor())
76        return;
77
78    HashSet<SVGElement*>::const_iterator it = m_referencedElements.begin();
79    HashSet<SVGElement*>::const_iterator end = m_referencedElements.end();
80
81    for (; it != end; ++it) {
82        SVGElement* referencedElement = *it;
83        referencedElement->cursorImageValueRemoved();
84        if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(toCSSImageValue(m_imageValue.get()).url(), referencedElement->document()))
85            cursorElement->removeClient(referencedElement);
86    }
87}
88
89String CSSCursorImageValue::customCSSText() const
90{
91    StringBuilder result;
92    result.append(m_imageValue.get().cssText());
93    if (m_hasHotSpot) {
94        result.append(' ');
95        result.appendNumber(m_hotSpot.x());
96        result.append(' ');
97        result.appendNumber(m_hotSpot.y());
98    }
99    return result.toString();
100}
101
102bool CSSCursorImageValue::updateIfSVGCursorIsUsed(Element* element)
103{
104    if (!element || !element->isSVGElement())
105        return false;
106
107    if (!isSVGCursor())
108        return false;
109
110    if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(toCSSImageValue(m_imageValue.get()).url(), element->document())) {
111        // FIXME: This will override hot spot specified in CSS, which is probably incorrect.
112        SVGLengthContext lengthContext(0);
113        m_hasHotSpot = true;
114        float x = roundf(cursorElement->x().value(lengthContext));
115        m_hotSpot.setX(static_cast<int>(x));
116
117        float y = roundf(cursorElement->y().value(lengthContext));
118        m_hotSpot.setY(static_cast<int>(y));
119
120        if (cachedImageURL() != element->document().completeURL(cursorElement->href()))
121            clearCachedImage();
122
123        SVGElement* svgElement = toSVGElement(element);
124        m_referencedElements.add(svgElement);
125        svgElement->setCursorImageValue(this);
126        cursorElement->addClient(svgElement);
127        return true;
128    }
129
130    return false;
131}
132
133StyleImage* CSSCursorImageValue::cachedImage(CachedResourceLoader* loader)
134{
135#if ENABLE(CSS_IMAGE_SET)
136    if (m_imageValue.get().isImageSetValue())
137        return toCSSImageSetValue(m_imageValue.get()).cachedImageSet(loader);
138#endif
139
140    if (!m_accessedImage) {
141        m_accessedImage = true;
142
143        // For SVG images we need to lazily substitute in the correct URL. Rather than attempt
144        // to change the URL of the CSSImageValue (which would then change behavior like cssText),
145        // we create an alternate CSSImageValue to use.
146        if (isSVGCursor() && loader && loader->document()) {
147            // FIXME: This will fail if the <cursor> element is in a shadow DOM (bug 59827)
148            if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(toCSSImageValue(m_imageValue.get()).url(), *loader->document())) {
149                detachPendingImage();
150                Ref<CSSImageValue> svgImageValue(CSSImageValue::create(cursorElement->href()));
151                StyleCachedImage* cachedImage = svgImageValue->cachedImage(loader);
152                m_image = cachedImage;
153                return cachedImage;
154            }
155        }
156
157        if (m_imageValue.get().isImageValue()) {
158            detachPendingImage();
159            m_image = toCSSImageValue(m_imageValue.get()).cachedImage(loader);
160        }
161    }
162
163    if (m_image && m_image->isCachedImage())
164        return toStyleCachedImage(m_image.get());
165
166    return nullptr;
167}
168
169StyleImage* CSSCursorImageValue::cachedOrPendingImage(Document& document)
170{
171#if ENABLE(CSS_IMAGE_SET)
172    // Need to delegate completely so that changes in device scale factor can be handled appropriately.
173    if (m_imageValue.get().isImageSetValue())
174        return toCSSImageSetValue(m_imageValue.get()).cachedOrPendingImageSet(document);
175#else
176    UNUSED_PARAM(document);
177#endif
178
179    if (!m_image)
180        m_image = StylePendingImage::create(this);
181
182    return m_image.get();
183}
184
185bool CSSCursorImageValue::isSVGCursor() const
186{
187    if (m_imageValue.get().isImageValue()) {
188        URL kurl(ParsedURLString, toCSSImageValue(m_imageValue.get()).url());
189        return kurl.hasFragmentIdentifier();
190    }
191    return false;
192}
193
194String CSSCursorImageValue::cachedImageURL()
195{
196    if (!m_image || !m_image->isCachedImage())
197        return String();
198    return toStyleCachedImage(*m_image).cachedImage()->url();
199}
200
201void CSSCursorImageValue::clearCachedImage()
202{
203    detachPendingImage();
204    m_image = nullptr;
205    m_accessedImage = false;
206}
207
208void CSSCursorImageValue::removeReferencedElement(SVGElement* element)
209{
210    m_referencedElements.remove(element);
211}
212
213bool CSSCursorImageValue::equals(const CSSCursorImageValue& other) const
214{
215    return m_hasHotSpot ? other.m_hasHotSpot && m_hotSpot == other.m_hotSpot : !other.m_hasHotSpot
216        && compareCSSValue(m_imageValue, other.m_imageValue);
217}
218
219} // namespace WebCore
220