1/*
2 * Copyright (C) 2006 Eric Seidel (eric@webkit.org)
3 * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#if ENABLE(SVG)
29#include "SVGImage.h"
30
31#include "CachedPage.h"
32#include "DocumentLoader.h"
33#include "FileChooser.h"
34#include "FloatRect.h"
35#include "Frame.h"
36#include "FrameLoader.h"
37#include "FrameView.h"
38#include "GraphicsContext.h"
39#include "HTMLFormElement.h"
40#include "ImageBuffer.h"
41#include "ImageObserver.h"
42#include "Page.h"
43#include "RenderView.h"
44#include "ResourceError.h"
45#include "SVGDocument.h"
46#include "SVGLength.h"
47#include "SVGRenderSupport.h"
48#include "SVGSVGElement.h"
49#include "Settings.h"
50
51// Moving this #include above FrameLoader.h causes the Windows build to fail due to warnings about
52// alignment in Timer<FrameLoader>. It seems that the definition of EmptyFrameLoaderClient is what
53// causes this (removing that definition fixes the warnings), but it isn't clear why.
54#include "EmptyClients.h"
55
56namespace WebCore {
57
58class SVGImageChromeClient : public EmptyChromeClient, public Noncopyable {
59public:
60    SVGImageChromeClient(SVGImage* image)
61        : m_image(image)
62    {
63    }
64
65    SVGImage* image() const { return m_image; }
66
67private:
68    virtual void chromeDestroyed()
69    {
70        m_image = 0;
71    }
72
73    virtual void repaint(const IntRect& r, bool, bool, bool)
74    {
75        if (m_image && m_image->imageObserver())
76            m_image->imageObserver()->changedInRect(m_image, r);
77    }
78
79    SVGImage* m_image;
80};
81
82SVGImage::SVGImage(ImageObserver* observer)
83    : Image(observer)
84{
85}
86
87SVGImage::~SVGImage()
88{
89    if (m_page) {
90        m_page->mainFrame()->loader()->frameDetached(); // Break both the loader and view references to the frame
91
92        // Clear explicitly because we want to delete the page before the ChromeClient.
93        // FIXME: I believe that's already guaranteed by C++ object destruction rules,
94        // so this may matter only for the assertion below.
95        m_page.clear();
96    }
97
98    // Verify that page teardown destroyed the Chrome
99    ASSERT(!m_chromeClient || !m_chromeClient->image());
100}
101
102void SVGImage::setContainerSize(const IntSize& containerSize)
103{
104    if (containerSize.isEmpty())
105        return;
106
107    if (!m_page)
108        return;
109    Frame* frame = m_page->mainFrame();
110    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
111    if (!rootElement)
112        return;
113
114    rootElement->setContainerSize(containerSize);
115}
116
117bool SVGImage::usesContainerSize() const
118{
119    if (!m_page)
120        return false;
121    Frame* frame = m_page->mainFrame();
122    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
123    if (!rootElement)
124        return false;
125
126    return rootElement->hasSetContainerSize();
127}
128
129IntSize SVGImage::size() const
130{
131    if (!m_page)
132        return IntSize();
133    Frame* frame = m_page->mainFrame();
134    SVGSVGElement* rootElement = static_cast<SVGDocument*>(frame->document())->rootElement();
135    if (!rootElement)
136        return IntSize();
137
138    SVGLength width = rootElement->width();
139    SVGLength height = rootElement->height();
140
141    IntSize svgSize;
142    if (width.unitType() == LengthTypePercentage)
143        svgSize.setWidth(rootElement->relativeWidthValue());
144    else
145        svgSize.setWidth(static_cast<int>(width.value(rootElement)));
146
147    if (height.unitType() == LengthTypePercentage)
148        svgSize.setHeight(rootElement->relativeHeightValue());
149    else
150        svgSize.setHeight(static_cast<int>(height.value(rootElement)));
151
152    return svgSize;
153}
154
155bool SVGImage::hasRelativeWidth() const
156{
157    if (!m_page)
158        return false;
159    SVGSVGElement* rootElement = static_cast<SVGDocument*>(m_page->mainFrame()->document())->rootElement();
160    if (!rootElement)
161        return false;
162
163    return rootElement->width().unitType() == LengthTypePercentage;
164}
165
166bool SVGImage::hasRelativeHeight() const
167{
168    if (!m_page)
169        return false;
170    SVGSVGElement* rootElement = static_cast<SVGDocument*>(m_page->mainFrame()->document())->rootElement();
171    if (!rootElement)
172        return false;
173
174    return rootElement->height().unitType() == LengthTypePercentage;
175}
176
177void SVGImage::draw(GraphicsContext* context, const FloatRect& dstRect, const FloatRect& srcRect, ColorSpace, CompositeOperator compositeOp)
178{
179    if (!m_page)
180        return;
181
182    FrameView* view = m_page->mainFrame()->view();
183
184    context->save();
185    context->setCompositeOperation(compositeOp);
186    context->clip(enclosingIntRect(dstRect));
187    if (compositeOp != CompositeSourceOver)
188        context->beginTransparencyLayer(1);
189    context->translate(dstRect.location().x(), dstRect.location().y());
190    context->scale(FloatSize(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height()));
191
192    view->resize(size());
193
194    if (view->needsLayout())
195        view->layout();
196    view->paint(context, IntRect(0, 0, view->width(), view->height()));
197
198    if (compositeOp != CompositeSourceOver)
199        context->endTransparencyLayer();
200
201    context->restore();
202
203    if (imageObserver())
204        imageObserver()->didDraw(this);
205}
206
207NativeImagePtr SVGImage::nativeImageForCurrentFrame()
208{
209    // FIXME: In order to support dynamic SVGs we need to have a way to invalidate this
210    // frame cache, or better yet, not use a cache for tiled drawing at all, instead
211    // having a tiled drawing callback (hopefully non-virtual).
212    if (!m_frameCache) {
213        if (!m_page)
214            return 0;
215        m_frameCache = ImageBuffer::create(size());
216        if (!m_frameCache) // failed to allocate image
217            return 0;
218        renderSubtreeToImage(m_frameCache.get(), m_page->mainFrame()->contentRenderer());
219    }
220    return m_frameCache->image()->nativeImageForCurrentFrame();
221}
222
223bool SVGImage::dataChanged(bool allDataReceived)
224{
225    // Don't do anything if is an empty image.
226    if (!data()->size())
227        return true;
228
229    if (allDataReceived) {
230        static FrameLoaderClient* dummyFrameLoaderClient =  new EmptyFrameLoaderClient;
231        static EditorClient* dummyEditorClient = new EmptyEditorClient;
232#if ENABLE(CONTEXT_MENUS)
233        static ContextMenuClient* dummyContextMenuClient = new EmptyContextMenuClient;
234#else
235        static ContextMenuClient* dummyContextMenuClient = 0;
236#endif
237#if ENABLE(DRAG_SUPPORT)
238        static DragClient* dummyDragClient = new EmptyDragClient;
239#else
240        static DragClient* dummyDragClient = 0;
241#endif
242        static InspectorClient* dummyInspectorClient = new EmptyInspectorClient;
243
244        m_chromeClient.set(new SVGImageChromeClient(this));
245
246        // FIXME: If this SVG ends up loading itself, we might leak the world.
247        // The comment said that the Cache code does not know about CachedImages
248        // holding Frames and won't know to break the cycle. But
249        m_page.set(new Page(m_chromeClient.get(), dummyContextMenuClient, dummyEditorClient, dummyDragClient, dummyInspectorClient, 0, 0));
250        m_page->settings()->setJavaScriptEnabled(false);
251        m_page->settings()->setPluginsEnabled(false);
252
253        RefPtr<Frame> frame = Frame::create(m_page.get(), 0, dummyFrameLoaderClient);
254        frame->setView(FrameView::create(frame.get()));
255        frame->init();
256        ResourceRequest fakeRequest(KURL(ParsedURLString, ""));
257        FrameLoader* loader = frame->loader();
258        loader->load(fakeRequest, false); // Make sure the DocumentLoader is created
259        loader->policyChecker()->cancelCheck(); // cancel any policy checks
260        loader->commitProvisionalLoad(0);
261        loader->setResponseMIMEType("image/svg+xml");
262        loader->begin(KURL()); // create the empty document
263        loader->write(data()->data(), data()->size());
264        loader->end();
265        frame->view()->setTransparent(true); // SVG Images are transparent.
266    }
267
268    return m_page;
269}
270
271String SVGImage::filenameExtension() const
272{
273    return "svg";
274}
275
276}
277
278#endif // ENABLE(SVG)
279