2 * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "core/html/ImageDocument.h"
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"
51 using namespace HTMLNames;
53 class ImageEventListener : public EventListener {
55 static PassRefPtr<ImageEventListener> create(ImageDocument* document) { return adoptRef(new ImageEventListener(document)); }
56 static const ImageEventListener* cast(const EventListener* listener)
58 return listener->type() == ImageEventListenerType
59 ? static_cast<const ImageEventListener*>(listener)
63 virtual bool operator==(const EventListener& other);
66 ImageEventListener(ImageDocument* document)
67 : EventListener(ImageEventListenerType)
72 virtual void handleEvent(ExecutionContext*, Event*);
77 class ImageDocumentParser : public RawDataDocumentParser {
79 static PassRefPtr<ImageDocumentParser> create(ImageDocument* document)
81 return adoptRef(new ImageDocumentParser(document));
84 ImageDocument* document() const
86 return toImageDocument(RawDataDocumentParser::document());
90 ImageDocumentParser(ImageDocument* document)
91 : RawDataDocumentParser(document)
95 virtual void appendBytes(const char*, size_t) OVERRIDE;
96 virtual void finish();
101 static float pageZoomFactor(const Document* document)
103 LocalFrame* frame = document->frame();
104 return frame ? frame->pageZoomFactor() : 1;
107 static String imageTitle(const String& filename, const IntSize& size)
109 StringBuilder result;
110 result.append(filename);
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()));
118 return result.toString();
121 void ImageDocumentParser::appendBytes(const char* data, size_t length)
126 LocalFrame* frame = document()->frame();
127 Settings* settings = frame->settings();
128 if (!frame->loader().client()->allowImage(!settings || settings->imagesEnabled(), document()->url()))
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();
138 void ImageDocumentParser::finish()
140 if (!isStopped() && document()->imageElement()) {
141 ImageResource* cachedImage = document()->cachedImage();
142 cachedImage->finish();
143 cachedImage->setResponse(document()->frame()->loader().documentLoader()->response());
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));
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));
157 document()->imageUpdated();
160 document()->finishedParsing();
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())
172 setCompatibilityMode(QuirksMode);
173 lockCompatibilityMode();
176 PassRefPtr<DocumentParser> ImageDocument::createParser()
178 return ImageDocumentParser::create(this);
181 void ImageDocument::createDocumentStructure()
183 RefPtr<HTMLHtmlElement> rootElement = HTMLHtmlElement::create(*this);
184 appendChild(rootElement);
185 rootElement->insertedByParser();
188 frame()->loader().dispatchDocumentElementAvailable();
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);
196 RefPtr<HTMLBodyElement> body = HTMLBodyElement::create(*this);
197 body->setAttribute(styleAttr, "margin: 0px;");
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());
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);
213 rootElement->appendChild(head);
214 rootElement->appendChild(body);
217 float ImageDocument::scale() const
219 if (!m_imageElement || m_imageElement->document() != this)
222 FrameView* view = frame()->view();
226 LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
227 LayoutSize windowSize = LayoutSize(view->width(), view->height());
229 float widthScale = windowSize.width().toFloat() / imageSize.width().toFloat();
230 float heightScale = windowSize.height().toFloat() / imageSize.height().toFloat();
232 return min(widthScale, heightScale);
235 void ImageDocument::resizeImageToFit()
237 if (!m_imageElement || m_imageElement->document() != this || pageZoomFactor(this) > 1)
240 LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
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));
246 m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomIn);
249 void ImageDocument::imageClicked(int x, int y)
251 if (!m_imageSizeIsKnown || imageFitsInWindow())
254 m_shouldShrinkImage = !m_shouldShrinkImage;
256 if (m_shouldShrinkImage)
263 float scale = this->scale();
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);
268 frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY));
272 void ImageDocument::imageUpdated()
274 ASSERT(m_imageElement);
276 if (m_imageSizeIsKnown)
279 if (m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this)).isEmpty())
282 m_imageSizeIsKnown = true;
284 if (shouldShrinkToFit()) {
285 // Force resizing of the image
290 void ImageDocument::restoreImageSize()
292 if (!m_imageElement || !m_imageSizeIsKnown || m_imageElement->document() != this || pageZoomFactor(this) < 1)
295 LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), 1.0f);
296 m_imageElement->setWidth(imageSize.width());
297 m_imageElement->setHeight(imageSize.height());
299 if (imageFitsInWindow())
300 m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
302 m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomOut);
304 m_didShrinkImage = false;
307 bool ImageDocument::imageFitsInWindow() const
309 if (!m_imageElement || m_imageElement->document() != this)
312 FrameView* view = frame()->view();
316 LayoutSize imageSize = m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), pageZoomFactor(this));
317 LayoutSize windowSize = LayoutSize(view->width(), view->height());
319 return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
322 void ImageDocument::windowSizeChanged()
324 if (!m_imageElement || !m_imageSizeIsKnown || m_imageElement->document() != this)
327 bool fitsInWindow = imageFitsInWindow();
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) {
333 m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
335 m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueWebkitZoomOut);
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.
347 // If the image isn't resized but needs to be, then resize it.
350 m_didShrinkImage = true;
355 ImageResource* ImageDocument::cachedImage()
358 createDocumentStructure();
360 return m_imageElement->cachedImage();
363 bool ImageDocument::shouldShrinkToFit() const
365 return frame()->settings()->shrinksStandaloneImagesToFit() && frame()->isMainFrame();
368 void ImageDocument::dispose()
370 m_imageElement = nullptr;
371 HTMLDocument::dispose();
376 void ImageEventListener::handleEvent(ExecutionContext*, Event* event)
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());
386 bool ImageEventListener::operator==(const EventListener& listener)
388 if (const ImageEventListener* imageEventListener = ImageEventListener::cast(&listener))
389 return m_doc == imageEventListener->m_doc;