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 "TreeScope.h"
32#include <wtf/MathExtras.h>
33#include <wtf/text/StringBuilder.h>
34#include <wtf/text/WTFString.h>
35
36#if ENABLE(SVG)
37#include "SVGCursorElement.h"
38#include "SVGLengthContext.h"
39#include "SVGNames.h"
40#include "SVGURIReference.h"
41#endif
42
43#if ENABLE(CSS_IMAGE_SET)
44#include "CSSImageSetValue.h"
45#include "StyleCachedImageSet.h"
46#endif
47
48namespace WebCore {
49
50#if ENABLE(SVG)
51static inline SVGCursorElement* resourceReferencedByCursorElement(const String& url, Document* document)
52{
53    Element* element = SVGURIReference::targetElementFromIRIString(url, document);
54    if (element && element->hasTagName(SVGNames::cursorTag))
55        return static_cast<SVGCursorElement*>(element);
56
57    return 0;
58}
59#endif
60
61CSSCursorImageValue::CSSCursorImageValue(PassRefPtr<CSSValue> imageValue, bool hasHotSpot, const IntPoint& hotSpot)
62    : CSSValue(CursorImageClass)
63    , m_imageValue(imageValue)
64    , m_hasHotSpot(hasHotSpot)
65    , m_hotSpot(hotSpot)
66    , m_accessedImage(false)
67{
68}
69
70inline void CSSCursorImageValue::detachPendingImage()
71{
72    if (m_image && m_image->isPendingImage())
73        static_cast<StylePendingImage&>(*m_image).detachFromCSSValue();
74}
75
76CSSCursorImageValue::~CSSCursorImageValue()
77{
78    detachPendingImage();
79
80#if ENABLE(SVG)
81    if (!isSVGCursor())
82        return;
83
84    HashSet<SVGElement*>::const_iterator it = m_referencedElements.begin();
85    HashSet<SVGElement*>::const_iterator end = m_referencedElements.end();
86    String url = static_cast<CSSImageValue*>(m_imageValue.get())->url();
87
88    for (; it != end; ++it) {
89        SVGElement* referencedElement = *it;
90        referencedElement->cursorImageValueRemoved();
91        if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(url, referencedElement->document()))
92            cursorElement->removeClient(referencedElement);
93    }
94#endif
95}
96
97String CSSCursorImageValue::customCssText() const
98{
99    StringBuilder result;
100    result.append(m_imageValue->cssText());
101    if (m_hasHotSpot) {
102        result.append(' ');
103        result.appendNumber(m_hotSpot.x());
104        result.append(' ');
105        result.appendNumber(m_hotSpot.y());
106    }
107    return result.toString();
108}
109
110bool CSSCursorImageValue::updateIfSVGCursorIsUsed(Element* element)
111{
112#if !ENABLE(SVG)
113    UNUSED_PARAM(element);
114#else
115    if (!element || !element->isSVGElement())
116        return false;
117
118    if (!isSVGCursor())
119        return false;
120
121    String url = static_cast<CSSImageValue*>(m_imageValue.get())->url();
122    if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(url, element->document())) {
123        // FIXME: This will override hot spot specified in CSS, which is probably incorrect.
124        SVGLengthContext lengthContext(0);
125        m_hasHotSpot = true;
126        float x = roundf(cursorElement->x().value(lengthContext));
127        m_hotSpot.setX(static_cast<int>(x));
128
129        float y = roundf(cursorElement->y().value(lengthContext));
130        m_hotSpot.setY(static_cast<int>(y));
131
132        if (cachedImageURL() != element->document()->completeURL(cursorElement->href()))
133            clearCachedImage();
134
135        SVGElement* svgElement = toSVGElement(element);
136        m_referencedElements.add(svgElement);
137        svgElement->setCursorImageValue(this);
138        cursorElement->addClient(svgElement);
139        return true;
140    }
141#endif
142
143    return false;
144}
145
146StyleImage* CSSCursorImageValue::cachedImage(CachedResourceLoader* loader)
147{
148#if ENABLE(CSS_IMAGE_SET)
149    if (m_imageValue->isImageSetValue())
150        return static_cast<CSSImageSetValue*>(m_imageValue.get())->cachedImageSet(loader);
151#endif
152
153    if (!m_accessedImage) {
154        m_accessedImage = true;
155
156#if ENABLE(SVG)
157        // For SVG images we need to lazily substitute in the correct URL. Rather than attempt
158        // to change the URL of the CSSImageValue (which would then change behavior like cssText),
159        // we create an alternate CSSImageValue to use.
160        if (isSVGCursor() && loader && loader->document()) {
161            RefPtr<CSSImageValue> imageValue = static_cast<CSSImageValue*>(m_imageValue.get());
162            // FIXME: This will fail if the <cursor> element is in a shadow DOM (bug 59827)
163            if (SVGCursorElement* cursorElement = resourceReferencedByCursorElement(imageValue->url(), loader->document())) {
164                detachPendingImage();
165                RefPtr<CSSImageValue> svgImageValue = CSSImageValue::create(cursorElement->href());
166                StyleCachedImage* cachedImage = svgImageValue->cachedImage(loader);
167                m_image = cachedImage;
168                return cachedImage;
169            }
170        }
171#endif
172
173        if (m_imageValue->isImageValue()) {
174            detachPendingImage();
175            m_image = static_cast<CSSImageValue*>(m_imageValue.get())->cachedImage(loader);
176        }
177    }
178
179    if (m_image && m_image->isCachedImage())
180        return static_cast<StyleCachedImage*>(m_image.get());
181
182    return 0;
183}
184
185StyleImage* CSSCursorImageValue::cachedOrPendingImage(Document* document)
186{
187#if ENABLE(CSS_IMAGE_SET)
188    // Need to delegate completely so that changes in device scale factor can be handled appropriately.
189    if (m_imageValue->isImageSetValue())
190        return static_cast<CSSImageSetValue*>(m_imageValue.get())->cachedOrPendingImageSet(document);
191#endif
192
193    if (!m_image)
194        m_image = StylePendingImage::create(this);
195
196    return m_image.get();
197}
198
199#if ENABLE(SVG)
200bool CSSCursorImageValue::isSVGCursor() const
201{
202    if (m_imageValue->isImageValue()) {
203        RefPtr<CSSImageValue> imageValue = static_cast<CSSImageValue*>(m_imageValue.get());
204        KURL kurl(ParsedURLString, imageValue->url());
205        return kurl.hasFragmentIdentifier();
206    }
207    return false;
208}
209
210String CSSCursorImageValue::cachedImageURL()
211{
212    if (!m_image || !m_image->isCachedImage())
213        return String();
214    return static_cast<StyleCachedImage*>(m_image.get())->cachedImage()->url();
215}
216
217void CSSCursorImageValue::clearCachedImage()
218{
219    detachPendingImage();
220    m_image = nullptr;
221    m_accessedImage = false;
222}
223
224void CSSCursorImageValue::removeReferencedElement(SVGElement* element)
225{
226    m_referencedElements.remove(element);
227}
228#endif
229
230bool CSSCursorImageValue::equals(const CSSCursorImageValue& other) const
231{
232    return m_hasHotSpot ? other.m_hasHotSpot && m_hotSpot == other.m_hotSpot : !other.m_hasHotSpot
233        && compareCSSValuePtr(m_imageValue, other.m_imageValue);
234}
235
236} // namespace WebCore
237