Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / ImageDocument.cpp
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 "core/html/ImageDocument.h"
27
28 #include "HTMLNames.h"
29 #include "bindings/v8/ExceptionStatePlaceholder.h"
30 #include "core/dom/RawDataDocumentParser.h"
31 #include "core/events/EventListener.h"
32 #include "core/events/MouseEvent.h"
33 #include "core/events/ThreadLocalEventNames.h"
34 #include "core/fetch/ImageResource.h"
35 #include "core/html/HTMLBodyElement.h"
36 #include "core/html/HTMLHeadElement.h"
37 #include "core/html/HTMLHtmlElement.h"
38 #include "core/html/HTMLImageElement.h"
39 #include "core/html/HTMLMetaElement.h"
40 #include "core/loader/DocumentLoader.h"
41 #include "core/loader/FrameLoader.h"
42 #include "core/loader/FrameLoaderClient.h"
43 #include "core/frame/Frame.h"
44 #include "core/frame/FrameView.h"
45 #include "core/frame/Settings.h"
46 #include "wtf/text/StringBuilder.h"
47
48 using std::min;
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 class ImageEventListener : public EventListener {
55 public:
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
66 private:
67     ImageEventListener(ImageDocument* document)
68         : EventListener(ImageEventListenerType)
69         , m_doc(document)
70     {
71     }
72
73     virtual void handleEvent(ExecutionContext*, Event*);
74
75     ImageDocument* m_doc;
76 };
77
78 class ImageDocumentParser : public RawDataDocumentParser {
79 public:
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
90 private:
91     ImageDocumentParser(ImageDocument* document)
92         : RawDataDocumentParser(document)
93     {
94     }
95
96     virtual void appendBytes(const char*, size_t) OVERRIDE;
97     virtual void finish();
98 };
99
100 // --------
101
102 static float pageZoomFactor(const Document* document)
103 {
104     Frame* frame = document->frame();
105     return frame ? frame->pageZoomFactor() : 1;
106 }
107
108 static String imageTitle(const String& filename, const IntSize& size)
109 {
110     StringBuilder result;
111     result.append(filename);
112     result.append(" (");
113     // FIXME: Localize numbers. Safari/OSX shows localized numbers with group
114     // separaters. For example, "1,920x1,080".
115     result.append(String::number(size.width()));
116     result.append(static_cast<UChar>(0xD7)); // U+00D7 (multiplication sign)
117     result.append(String::number(size.height()));
118     result.append(')');
119     return result.toString();
120 }
121
122 void ImageDocumentParser::appendBytes(const char* data, size_t length)
123 {
124     if (!length)
125         return;
126
127     Frame* frame = document()->frame();
128     Settings* settings = frame->settings();
129     if (!frame->loader().client()->allowImage(!settings || settings->imagesEnabled(), document()->url()))
130         return;
131
132     document()->cachedImage()->appendData(data, length);
133     document()->imageUpdated();
134 }
135
136 void ImageDocumentParser::finish()
137 {
138     if (!isStopped() && document()->imageElement()) {
139         ImageResource* cachedImage = document()->cachedImage();
140         cachedImage->finish();
141         cachedImage->setResponse(document()->frame()->loader().documentLoader()->response());
142
143         // Report the natural image size in the page title, regardless of zoom level.
144         // At a zoom level of 1 the image is guaranteed to have an integer size.
145         IntSize size = flooredIntSize(cachedImage->imageSizeForRenderer(document()->imageElement()->renderer(), 1.0f));
146         if (size.width()) {
147             // Compute the title, we use the decoded filename of the resource, falling
148             // back on the (decoded) hostname if there is no path.
149             String fileName = decodeURLEscapeSequences(document()->url().lastPathComponent());
150             if (fileName.isEmpty())
151                 fileName = document()->url().host();
152             document()->setTitle(imageTitle(fileName, size));
153         }
154
155         document()->imageUpdated();
156     }
157
158     document()->finishedParsing();
159 }
160
161 // --------
162
163 ImageDocument::ImageDocument(const DocumentInit& initializer)
164     : HTMLDocument(initializer, ImageDocumentClass)
165     , m_imageElement(0)
166     , m_imageSizeIsKnown(false)
167     , m_didShrinkImage(false)
168     , m_shouldShrinkImage(shouldShrinkToFit())
169 {
170     setCompatibilityMode(QuirksMode);
171     lockCompatibilityMode();
172 }
173
174 PassRefPtr<DocumentParser> ImageDocument::createParser()
175 {
176     return ImageDocumentParser::create(this);
177 }
178
179 void ImageDocument::createDocumentStructure()
180 {
181     RefPtr<HTMLHtmlElement> rootElement = HTMLHtmlElement::create(*this);
182     appendChild(rootElement);
183     rootElement->insertedByParser();
184
185     if (frame())
186         frame()->loader().dispatchDocumentElementAvailable();
187
188     RefPtr<HTMLHeadElement> head = HTMLHeadElement::create(*this);
189     RefPtr<HTMLMetaElement> meta = HTMLMetaElement::create(*this);
190     meta->setAttribute(nameAttr, "viewport");
191     meta->setAttribute(contentAttr, "width=device-width, minimum-scale=0.1");
192     head->appendChild(meta);
193
194     RefPtr<HTMLBodyElement> body = HTMLBodyElement::create(*this);
195     body->setAttribute(styleAttr, "margin: 0px;");
196
197     m_imageElement = HTMLImageElement::create(*this);
198     m_imageElement->setAttribute(styleAttr, "-webkit-user-select: none");
199     m_imageElement->setLoadManually(true);
200     m_imageElement->setSrc(url().string());
201     body->appendChild(m_imageElement.get());
202
203     if (shouldShrinkToFit()) {
204         // Add event listeners
205         RefPtr<EventListener> listener = ImageEventListener::create(this);
206         if (DOMWindow* domWindow = this->domWindow())
207             domWindow->addEventListener("resize", listener, false);
208         m_imageElement->addEventListener("click", listener.release(), false);
209     }
210
211     rootElement->appendChild(head);
212     rootElement->appendChild(body);
213 }
214
215 float ImageDocument::scale() const
216 {
217     if (!m_imageElement || m_imageElement->document() != this)
218         return 1.0f;
219
220     FrameView* view = frame()->view();
221     if (!view)
222         return 1;
223
224     LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
225     LayoutSize windowSize = LayoutSize(view->width(), view->height());
226
227     float widthScale = (float)windowSize.width() / imageSize.width();
228     float heightScale = (float)windowSize.height() / imageSize.height();
229
230     return min(widthScale, heightScale);
231 }
232
233 void ImageDocument::resizeImageToFit()
234 {
235     if (!m_imageElement || m_imageElement->document() != this || pageZoomFactor(this) > 1)
236         return;
237
238     LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
239
240     float scale = this->scale();
241     m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
242     m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
243
244     m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomIn);
245 }
246
247 void ImageDocument::imageClicked(int x, int y)
248 {
249     if (!m_imageSizeIsKnown || imageFitsInWindow())
250         return;
251
252     m_shouldShrinkImage = !m_shouldShrinkImage;
253
254     if (m_shouldShrinkImage)
255         windowSizeChanged();
256     else {
257         restoreImageSize();
258
259         updateLayout();
260
261         float scale = this->scale();
262
263         int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
264         int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
265
266         frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY));
267     }
268 }
269
270 void ImageDocument::imageUpdated()
271 {
272     ASSERT(m_imageElement);
273
274     if (m_imageSizeIsKnown)
275         return;
276
277     if (m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this)).isEmpty())
278         return;
279
280     m_imageSizeIsKnown = true;
281
282     if (shouldShrinkToFit()) {
283         // Force resizing of the image
284         windowSizeChanged();
285     }
286 }
287
288 void ImageDocument::restoreImageSize()
289 {
290     if (!m_imageElement || !m_imageSizeIsKnown || m_imageElement->document() != this || pageZoomFactor(this) < 1)
291         return;
292
293     LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), 1.0f);
294     m_imageElement->setWidth(imageSize.width());
295     m_imageElement->setHeight(imageSize.height());
296
297     if (imageFitsInWindow())
298         m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
299     else
300         m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomOut);
301
302     m_didShrinkImage = false;
303 }
304
305 bool ImageDocument::imageFitsInWindow() const
306 {
307     if (!m_imageElement || m_imageElement->document() != this)
308         return true;
309
310     FrameView* view = frame()->view();
311     if (!view)
312         return true;
313
314     LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
315     LayoutSize windowSize = LayoutSize(view->width(), view->height());
316
317     return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
318 }
319
320 void ImageDocument::windowSizeChanged()
321 {
322     if (!m_imageElement || !m_imageSizeIsKnown || m_imageElement->document() != this)
323         return;
324
325     bool fitsInWindow = imageFitsInWindow();
326
327     // If the image has been explicitly zoomed in, restore the cursor if the image fits
328     // and set it to a zoom out cursor if the image doesn't fit
329     if (!m_shouldShrinkImage) {
330         if (fitsInWindow)
331             m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
332         else
333             m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomOut);
334         return;
335     }
336
337     if (m_didShrinkImage) {
338         // If the window has been resized so that the image fits, restore the image size
339         // otherwise update the restored image size.
340         if (fitsInWindow)
341             restoreImageSize();
342         else
343             resizeImageToFit();
344     } else {
345         // If the image isn't resized but needs to be, then resize it.
346         if (!fitsInWindow) {
347             resizeImageToFit();
348             m_didShrinkImage = true;
349         }
350     }
351 }
352
353 ImageResource* ImageDocument::cachedImage()
354 {
355     if (!m_imageElement)
356         createDocumentStructure();
357
358     return m_imageElement->cachedImage();
359 }
360
361 bool ImageDocument::shouldShrinkToFit() const
362 {
363     return frame()->settings()->shrinksStandaloneImagesToFit() && frame()->isMainFrame();
364 }
365
366 void ImageDocument::dispose()
367 {
368     m_imageElement = 0;
369     HTMLDocument::dispose();
370 }
371
372 // --------
373
374 void ImageEventListener::handleEvent(ExecutionContext*, Event* event)
375 {
376     if (event->type() == EventTypeNames::resize)
377         m_doc->windowSizeChanged();
378     else if (event->type() == EventTypeNames::click && event->isMouseEvent()) {
379         MouseEvent* mouseEvent = toMouseEvent(event);
380         m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
381     }
382 }
383
384 bool ImageEventListener::operator==(const EventListener& listener)
385 {
386     if (const ImageEventListener* imageEventListener = ImageEventListener::cast(&listener))
387         return m_doc == imageEventListener->m_doc;
388     return false;
389 }
390
391 }