1/*
2 * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "ImageDocument.h"
27
28#include "CachedImage.h"
29#include "DocumentLoader.h"
30#include "EventListener.h"
31#include "EventNames.h"
32#include "ExceptionCodePlaceholder.h"
33#include "Frame.h"
34#include "FrameLoader.h"
35#include "FrameLoaderClient.h"
36#include "FrameView.h"
37#include "HTMLHtmlElement.h"
38#include "HTMLImageElement.h"
39#include "HTMLNames.h"
40#include "LocalizedStrings.h"
41#include "MouseEvent.h"
42#include "NotImplemented.h"
43#include "Page.h"
44#include "RawDataDocumentParser.h"
45#include "ResourceBuffer.h"
46#include "Settings.h"
47
48using std::min;
49
50namespace WebCore {
51
52using namespace HTMLNames;
53
54class ImageEventListener : public EventListener {
55public:
56    static PassRefPtr<ImageEventListener> create(ImageDocument* document) { return adoptRef(new ImageEventListener(document)); }
57    static const ImageEventListener* cast(const EventListener* listener)
58    {
59        return listener->type() == ImageEventListenerType
60            ? static_cast<const ImageEventListener*>(listener)
61            : 0;
62    }
63
64    virtual bool operator==(const EventListener& other);
65
66private:
67    ImageEventListener(ImageDocument* document)
68        : EventListener(ImageEventListenerType)
69        , m_doc(document)
70    {
71    }
72
73    virtual void handleEvent(ScriptExecutionContext*, Event*);
74
75    ImageDocument* m_doc;
76};
77
78class ImageDocumentParser : public RawDataDocumentParser {
79public:
80    static PassRefPtr<ImageDocumentParser> create(ImageDocument* document)
81    {
82        return adoptRef(new ImageDocumentParser(document));
83    }
84
85    ImageDocument* document() const
86    {
87        return toImageDocument(RawDataDocumentParser::document());
88    }
89
90private:
91    ImageDocumentParser(ImageDocument* document)
92        : RawDataDocumentParser(document)
93    {
94    }
95
96    virtual void appendBytes(DocumentWriter*, const char*, size_t);
97    virtual void finish();
98};
99
100class ImageDocumentElement FINAL : public HTMLImageElement {
101public:
102    static PassRefPtr<ImageDocumentElement> create(ImageDocument*);
103
104private:
105    ImageDocumentElement(ImageDocument* document)
106        : HTMLImageElement(imgTag, document)
107        , m_imageDocument(document)
108    {
109    }
110
111    virtual ~ImageDocumentElement();
112    virtual void didMoveToNewDocument(Document* oldDocument) OVERRIDE;
113
114    ImageDocument* m_imageDocument;
115};
116
117inline PassRefPtr<ImageDocumentElement> ImageDocumentElement::create(ImageDocument* document)
118{
119    return adoptRef(new ImageDocumentElement(document));
120}
121
122// --------
123
124static float pageZoomFactor(const Document* document)
125{
126    Frame* frame = document->frame();
127    return frame ? frame->pageZoomFactor() : 1;
128}
129
130void ImageDocumentParser::appendBytes(DocumentWriter*, const char*, size_t)
131{
132    Frame* frame = document()->frame();
133    Settings* settings = frame->settings();
134    if (!frame->loader()->client()->allowImage(!settings || settings->areImagesEnabled(), document()->url()))
135        return;
136
137    CachedImage* cachedImage = document()->cachedImage();
138    RefPtr<ResourceBuffer> resourceData = frame->loader()->documentLoader()->mainResourceData();
139    cachedImage->addDataBuffer(resourceData.get());
140
141    document()->imageUpdated();
142}
143
144void ImageDocumentParser::finish()
145{
146    if (!isStopped() && document()->imageElement()) {
147        CachedImage* cachedImage = document()->cachedImage();
148        RefPtr<ResourceBuffer> data = document()->frame()->loader()->documentLoader()->mainResourceData();
149
150        // If this is a multipart image, make a copy of the current part, since the resource data
151        // will be overwritten by the next part.
152        if (document()->frame()->loader()->documentLoader()->isLoadingMultipartContent())
153            data = data->copy();
154
155        cachedImage->finishLoading(data.get());
156        cachedImage->finish();
157
158        cachedImage->setResponse(document()->frame()->loader()->documentLoader()->response());
159
160        // Report the natural image size in the page title, regardless of zoom level.
161        // At a zoom level of 1 the image is guaranteed to have an integer size.
162        IntSize size = flooredIntSize(cachedImage->imageSizeForRenderer(document()->imageElement()->renderer(), 1.0f));
163        if (size.width()) {
164            // Compute the title, we use the decoded filename of the resource, falling
165            // back on the (decoded) hostname if there is no path.
166            String fileName = decodeURLEscapeSequences(document()->url().lastPathComponent());
167            if (fileName.isEmpty())
168                fileName = document()->url().host();
169            document()->setTitle(imageTitle(fileName, size));
170        }
171
172        document()->imageUpdated();
173    }
174
175    document()->finishedParsing();
176}
177
178// --------
179
180ImageDocument::ImageDocument(Frame* frame, const KURL& url)
181    : HTMLDocument(frame, url, ImageDocumentClass)
182    , m_imageElement(0)
183    , m_imageSizeIsKnown(false)
184    , m_didShrinkImage(false)
185    , m_shouldShrinkImage(shouldShrinkToFit())
186{
187    setCompatibilityMode(QuirksMode);
188    lockCompatibilityMode();
189}
190
191PassRefPtr<DocumentParser> ImageDocument::createParser()
192{
193    return ImageDocumentParser::create(this);
194}
195
196void ImageDocument::createDocumentStructure()
197{
198    RefPtr<Element> rootElement = Document::createElement(htmlTag, false);
199    appendChild(rootElement, IGNORE_EXCEPTION);
200    static_cast<HTMLHtmlElement*>(rootElement.get())->insertedByParser();
201
202    if (frame() && frame()->loader())
203        frame()->loader()->dispatchDocumentElementAvailable();
204
205    RefPtr<Element> body = Document::createElement(bodyTag, false);
206    body->setAttribute(styleAttr, "margin: 0px;");
207
208    rootElement->appendChild(body, IGNORE_EXCEPTION);
209
210    RefPtr<ImageDocumentElement> imageElement = ImageDocumentElement::create(this);
211
212    imageElement->setAttribute(styleAttr, "-webkit-user-select: none");
213    imageElement->setLoadManually(true);
214    imageElement->setSrc(url().string());
215
216    body->appendChild(imageElement, IGNORE_EXCEPTION);
217
218    if (shouldShrinkToFit()) {
219        // Add event listeners
220        RefPtr<EventListener> listener = ImageEventListener::create(this);
221        if (DOMWindow* domWindow = this->domWindow())
222            domWindow->addEventListener("resize", listener, false);
223        imageElement->addEventListener("click", listener.release(), false);
224    }
225
226    m_imageElement = imageElement.get();
227}
228
229float ImageDocument::scale() const
230{
231    if (!m_imageElement)
232        return 1.0f;
233
234    FrameView* view = frame()->view();
235    if (!view)
236        return 1;
237
238    LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
239    LayoutSize windowSize = LayoutSize(view->width(), view->height());
240
241    float widthScale = (float)windowSize.width() / imageSize.width();
242    float heightScale = (float)windowSize.height() / imageSize.height();
243
244    return min(widthScale, heightScale);
245}
246
247void ImageDocument::resizeImageToFit()
248{
249    if (!m_imageElement)
250        return;
251
252    LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
253
254    float scale = this->scale();
255    m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
256    m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
257
258    m_imageElement->setInlineStyleProperty(CSSPropertyCursor, "-webkit-zoom-in", false);
259}
260
261void ImageDocument::imageClicked(int x, int y)
262{
263    if (!m_imageSizeIsKnown || imageFitsInWindow())
264        return;
265
266    m_shouldShrinkImage = !m_shouldShrinkImage;
267
268    if (m_shouldShrinkImage)
269        windowSizeChanged();
270    else {
271        restoreImageSize();
272
273        updateLayout();
274
275        float scale = this->scale();
276
277        int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
278        int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
279
280        frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY));
281    }
282}
283
284void ImageDocument::imageUpdated()
285{
286    ASSERT(m_imageElement);
287
288    if (m_imageSizeIsKnown)
289        return;
290
291    if (m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this)).isEmpty())
292        return;
293
294    m_imageSizeIsKnown = true;
295
296    if (shouldShrinkToFit()) {
297        // Force resizing of the image
298        windowSizeChanged();
299    }
300}
301
302void ImageDocument::restoreImageSize()
303{
304    if (!m_imageElement || !m_imageSizeIsKnown)
305        return;
306
307    LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
308    m_imageElement->setWidth(imageSize.width());
309    m_imageElement->setHeight(imageSize.height());
310
311    if (imageFitsInWindow())
312        m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
313    else
314        m_imageElement->setInlineStyleProperty(CSSPropertyCursor, "-webkit-zoom-out", false);
315
316    m_didShrinkImage = false;
317}
318
319bool ImageDocument::imageFitsInWindow() const
320{
321    if (!m_imageElement)
322        return true;
323
324    FrameView* view = frame()->view();
325    if (!view)
326        return true;
327
328    LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
329    LayoutSize windowSize = LayoutSize(view->width(), view->height());
330
331    return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
332}
333
334void ImageDocument::windowSizeChanged()
335{
336    if (!m_imageElement || !m_imageSizeIsKnown)
337        return;
338
339    bool fitsInWindow = imageFitsInWindow();
340
341    // If the image has been explicitly zoomed in, restore the cursor if the image fits
342    // and set it to a zoom out cursor if the image doesn't fit
343    if (!m_shouldShrinkImage) {
344        if (fitsInWindow)
345            m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
346        else
347            m_imageElement->setInlineStyleProperty(CSSPropertyCursor, "-webkit-zoom-out", false);
348        return;
349    }
350
351    if (m_didShrinkImage) {
352        // If the window has been resized so that the image fits, restore the image size
353        // otherwise update the restored image size.
354        if (fitsInWindow)
355            restoreImageSize();
356        else
357            resizeImageToFit();
358    } else {
359        // If the image isn't resized but needs to be, then resize it.
360        if (!fitsInWindow) {
361            resizeImageToFit();
362            m_didShrinkImage = true;
363        }
364    }
365}
366
367CachedImage* ImageDocument::cachedImage()
368{
369    if (!m_imageElement)
370        createDocumentStructure();
371
372    return m_imageElement->cachedImage();
373}
374
375bool ImageDocument::shouldShrinkToFit() const
376{
377    return frame()->page()->settings()->shrinksStandaloneImagesToFit() &&
378        frame()->page()->mainFrame() == frame();
379}
380
381// --------
382
383void ImageEventListener::handleEvent(ScriptExecutionContext*, Event* event)
384{
385    if (event->type() == eventNames().resizeEvent)
386        m_doc->windowSizeChanged();
387    else if (event->type() == eventNames().clickEvent && event->isMouseEvent()) {
388        MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
389        m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
390    }
391}
392
393bool ImageEventListener::operator==(const EventListener& listener)
394{
395    if (const ImageEventListener* imageEventListener = ImageEventListener::cast(&listener))
396        return m_doc == imageEventListener->m_doc;
397    return false;
398}
399
400// --------
401
402ImageDocumentElement::~ImageDocumentElement()
403{
404    if (m_imageDocument)
405        m_imageDocument->disconnectImageElement();
406}
407
408void ImageDocumentElement::didMoveToNewDocument(Document* oldDocument)
409{
410    if (m_imageDocument) {
411        m_imageDocument->disconnectImageElement();
412        m_imageDocument = 0;
413    }
414    HTMLImageElement::didMoveToNewDocument(oldDocument);
415}
416
417}
418