Reviewed by Mitz.
[WebKit-https.git] / WebCore / loader / ImageDocument.cpp
1 /*
2  * Copyright (C) 2006, 2007 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 "Element.h"
31 #include "EventListener.h"
32 #include "EventNames.h"
33 #include "Frame.h"
34 #include "FrameLoader.h"
35 #include "FrameView.h"
36 #include "HTMLImageElement.h"
37 #include "HTMLNames.h"
38 #include "MouseEvent.h"
39 #include "Page.h"
40 #include "SegmentedString.h"
41 #include "Settings.h"
42 #include "Text.h"
43 #include "XMLTokenizer.h"
44
45 #if PLATFORM(MAC)
46 #include "ImageDocumentMac.h"
47 #endif 
48
49 using std::min;
50
51 namespace WebCore {
52
53 using namespace EventNames;
54 using namespace HTMLNames;
55
56 class ImageEventListener : public EventListener {
57 public:
58     ImageEventListener(ImageDocument* doc) : m_doc(doc) { }
59     virtual void handleEvent(Event*, bool isWindowEvent);
60
61 private:
62     ImageDocument* m_doc;
63 };
64     
65 class ImageTokenizer : public Tokenizer {
66 public:
67     ImageTokenizer(ImageDocument* doc) : m_doc(doc) {}
68
69     virtual bool write(const SegmentedString&, bool appendData);
70     virtual void finish();
71     virtual bool isWaitingForScripts() const;
72     
73     virtual bool wantsRawData() const { return true; }
74     virtual bool writeRawData(const char* data, int len);
75
76 private:
77     ImageDocument* m_doc;
78 };
79
80 class ImageDocumentElement : public HTMLImageElement {
81 public:
82     ImageDocumentElement(ImageDocument* doc) : HTMLImageElement(doc), m_imageDocument(doc) { }
83     virtual ~ImageDocumentElement();
84     virtual void willMoveToNewOwnerDocument();
85
86 private:
87     ImageDocument* m_imageDocument;
88 };
89
90 // --------
91
92 bool ImageTokenizer::write(const SegmentedString& s, bool appendData)
93 {
94     ASSERT_NOT_REACHED();
95     return false;
96 }
97
98 bool ImageTokenizer::writeRawData(const char* data, int len)
99 {
100     CachedImage* cachedImage = m_doc->cachedImage();
101     cachedImage->data(m_doc->frame()->loader()->documentLoader()->mainResourceData(), false);
102
103     m_doc->imageChanged();
104     
105     return false;
106 }
107
108 void ImageTokenizer::finish()
109 {
110     if (!m_parserStopped && m_doc->imageElement()) {
111         CachedImage* cachedImage = m_doc->cachedImage();
112         RefPtr<SharedBuffer> data = m_doc->frame()->loader()->documentLoader()->mainResourceData();
113
114         // If this is a multipart image, make a copy of the current part, since the resource data
115         // will be overwritten by the next part.
116         if (m_doc->frame()->loader()->documentLoader()->isLoadingMultipartContent())
117             data = new SharedBuffer(data->data(), data->size());
118
119         cachedImage->data(data.release(), true);
120         cachedImage->finish();
121
122         cachedImage->setResponse(m_doc->frame()->loader()->documentLoader()->response());
123         
124         // FIXME: Need code to set the title for platforms other than Mac OS X.
125 #if PLATFORM(MAC)
126         finishImageLoad(m_doc, cachedImage);
127 #endif
128     }
129
130     m_doc->finishedParsing();
131 }
132     
133 bool ImageTokenizer::isWaitingForScripts() const
134 {
135     // An image document is never waiting for scripts
136     return false;
137 }
138     
139 // --------
140
141 ImageDocument::ImageDocument(DOMImplementation* implementation, Frame* frame)
142     : HTMLDocument(implementation, frame)
143     , m_imageElement(0)
144     , m_imageSizeIsKnown(false)
145     , m_didShrinkImage(false)
146     , m_shouldShrinkImage(shouldShrinkToFit())
147 {
148     setParseMode(Compat);
149 }
150     
151 Tokenizer* ImageDocument::createTokenizer()
152 {
153     return new ImageTokenizer(this);
154 }
155
156 void ImageDocument::createDocumentStructure()
157 {
158     ExceptionCode ec;
159     
160     RefPtr<Element> rootElement = createElementNS(xhtmlNamespaceURI, "html", ec);
161     appendChild(rootElement, ec);
162     
163     RefPtr<Element> body = createElementNS(xhtmlNamespaceURI, "body", ec);
164     body->setAttribute(styleAttr, "margin: 0px;");
165     
166     rootElement->appendChild(body, ec);
167     
168     RefPtr<ImageDocumentElement> imageElement = new ImageDocumentElement(this);
169     
170     imageElement->setAttribute(styleAttr, "-webkit-user-select: none");        
171     imageElement->setLoadManually(true);
172     imageElement->setSrc(URL());
173     
174     body->appendChild(imageElement, ec);
175     
176     if (shouldShrinkToFit()) {
177         // Add event listeners
178         RefPtr<EventListener> listener = new ImageEventListener(this);
179         addWindowEventListener("resize", listener, false);
180         imageElement->addEventListener("click", listener.release(), false);
181     }
182
183     m_imageElement = imageElement.get();
184 }
185
186 float ImageDocument::scale() const
187 {
188     if (!m_imageElement)
189         return 1.0f;
190
191     IntSize imageSize = m_imageElement->cachedImage()->imageSize();
192     IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
193     
194     float widthScale = (float)windowSize.width() / imageSize.width();
195     float heightScale = (float)windowSize.height() / imageSize.height();
196
197     return min(widthScale, heightScale);
198 }
199
200 void ImageDocument::resizeImageToFit()
201 {
202     if (!m_imageElement)
203         return;
204
205     IntSize imageSize = m_imageElement->cachedImage()->imageSize();
206
207     float scale = this->scale();
208     m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
209     m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
210     
211     ExceptionCode ec;
212     m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec);
213 }
214
215 void ImageDocument::imageClicked(int x, int y)
216 {
217     if (!m_imageSizeIsKnown || imageFitsInWindow())
218         return;
219
220     m_shouldShrinkImage = !m_shouldShrinkImage;
221     
222     if (m_shouldShrinkImage)
223         windowSizeChanged();
224     else {
225         restoreImageSize();
226         
227         updateLayout();
228         
229         float scale = this->scale();
230         
231         int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
232         int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
233         
234         frame()->view()->setContentsPos(scrollX, scrollY);
235     }
236 }
237
238 void ImageDocument::imageChanged()
239 {
240     ASSERT(m_imageElement);
241     
242     if (m_imageSizeIsKnown)
243         return;
244
245     if (m_imageElement->cachedImage()->imageSize().isEmpty())
246         return;
247     
248     m_imageSizeIsKnown = true;
249     
250     if (shouldShrinkToFit()) {
251         // Force resizing of the image
252         windowSizeChanged();
253     }
254 }
255
256 void ImageDocument::restoreImageSize()
257 {
258     if (!m_imageElement || !m_imageSizeIsKnown)
259         return;
260     
261     m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize().width());
262     m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize().height());
263     
264     ExceptionCode ec;
265     if (imageFitsInWindow())
266         m_imageElement->style()->removeProperty("cursor", ec);
267     else
268         m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
269         
270     m_didShrinkImage = false;
271 }
272
273 bool ImageDocument::imageFitsInWindow() const
274 {
275     if (!m_imageElement)
276         return true;
277
278     IntSize imageSize = m_imageElement->cachedImage()->imageSize();
279     IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
280     
281     return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();    
282 }
283
284 void ImageDocument::windowSizeChanged()
285 {
286     if (!m_imageElement || !m_imageSizeIsKnown)
287         return;
288
289     bool fitsInWindow = imageFitsInWindow();
290     
291     // If the image has been explicitly zoomed in, restore the cursor if the image fits
292     // and set it to a zoom out cursor if the image doesn't fit
293     if (!m_shouldShrinkImage) {
294         ExceptionCode ec;
295         
296         if (fitsInWindow)
297             m_imageElement->style()->removeProperty("cursor", ec);
298         else
299             m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
300         return;
301     }
302     
303     if (m_didShrinkImage) {
304         // If the window has been resized so that the image fits, restore the image size
305         // otherwise update the restored image size.
306         if (fitsInWindow)
307             restoreImageSize();
308         else
309             resizeImageToFit();
310     } else {
311         // If the image isn't resized but needs to be, then resize it.
312         if (!fitsInWindow) {
313             resizeImageToFit();
314             m_didShrinkImage = true;
315         }
316     }
317 }
318
319 CachedImage* ImageDocument::cachedImage()
320
321     if (!m_imageElement)
322         createDocumentStructure();
323     
324     return m_imageElement->cachedImage();
325 }
326
327 bool ImageDocument::shouldShrinkToFit() const
328 {
329     return frame()->page()->settings()->shrinksStandaloneImagesToFit() &&
330         frame()->page()->mainFrame() == frame();
331 }
332
333 // --------
334
335 void ImageEventListener::handleEvent(Event* event, bool isWindowEvent)
336 {
337     if (event->type() == resizeEvent)
338         m_doc->windowSizeChanged();
339     else if (event->type() == clickEvent) {
340         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
341         m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
342     }
343 }
344
345 // --------
346
347 ImageDocumentElement::~ImageDocumentElement()
348 {
349     if (m_imageDocument)
350         m_imageDocument->disconnectImageElement();
351 }
352
353 void ImageDocumentElement::willMoveToNewOwnerDocument()
354 {
355     if (m_imageDocument) {
356         m_imageDocument->disconnectImageElement();
357         m_imageDocument = 0;
358     }
359     HTMLImageElement::willMoveToNewOwnerDocument();
360 }
361
362 }