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