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