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 "bindings/core/v8/ExceptionStatePlaceholder.h"
29 #include "core/HTMLNames.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/FrameHost.h"
35 #include "core/frame/FrameView.h"
36 #include "core/frame/LocalDOMWindow.h"
37 #include "core/frame/LocalFrame.h"
38 #include "core/frame/Settings.h"
39 #include "core/frame/VisualViewport.h"
40 #include "core/html/HTMLBodyElement.h"
41 #include "core/html/HTMLHeadElement.h"
42 #include "core/html/HTMLHtmlElement.h"
43 #include "core/html/HTMLImageElement.h"
44 #include "core/html/HTMLMetaElement.h"
45 #include "core/loader/DocumentLoader.h"
46 #include "core/loader/FrameLoader.h"
47 #include "core/loader/FrameLoaderClient.h"
48 #include "wtf/text/StringBuilder.h"
55 using namespace HTMLNames
;
57 class ImageEventListener
: public EventListener
{
59 static PassRefPtrWillBeRawPtr
<ImageEventListener
> create(ImageDocument
* document
)
61 return adoptRefWillBeNoop(new ImageEventListener(document
));
63 static const ImageEventListener
* cast(const EventListener
* listener
)
65 return listener
->type() == ImageEventListenerType
66 ? static_cast<const ImageEventListener
*>(listener
)
70 virtual bool operator==(const EventListener
& other
);
72 DEFINE_INLINE_VIRTUAL_TRACE()
74 visitor
->trace(m_doc
);
75 EventListener::trace(visitor
);
79 ImageEventListener(ImageDocument
* document
)
80 : EventListener(ImageEventListenerType
)
85 virtual void handleEvent(ExecutionContext
*, Event
*);
87 RawPtrWillBeMember
<ImageDocument
> m_doc
;
90 class ImageDocumentParser
: public RawDataDocumentParser
{
92 static PassRefPtrWillBeRawPtr
<ImageDocumentParser
> create(ImageDocument
* document
)
94 return adoptRefWillBeNoop(new ImageDocumentParser(document
));
97 ImageDocument
* document() const
99 return toImageDocument(RawDataDocumentParser::document());
103 ImageDocumentParser(ImageDocument
* document
)
104 : RawDataDocumentParser(document
)
108 void appendBytes(const char*, size_t) override
;
109 virtual void finish();
114 static float pageZoomFactor(const Document
* document
)
116 LocalFrame
* frame
= document
->frame();
117 return frame
? frame
->pageZoomFactor() : 1;
120 static String
imageTitle(const String
& filename
, const IntSize
& size
)
122 StringBuilder result
;
123 result
.append(filename
);
124 result
.appendLiteral(" (");
125 // FIXME: Localize numbers. Safari/OSX shows localized numbers with group
126 // separaters. For example, "1,920x1,080".
127 result
.appendNumber(size
.width());
128 result
.append(static_cast<UChar
>(0xD7)); // U+00D7 (multiplication sign)
129 result
.appendNumber(size
.height());
131 return result
.toString();
134 void ImageDocumentParser::appendBytes(const char* data
, size_t length
)
139 LocalFrame
* frame
= document()->frame();
140 Settings
* settings
= frame
->settings();
141 if (!frame
->loader().client()->allowImage(!settings
|| settings
->imagesEnabled(), document()->url()))
144 if (document()->cachedImage()) {
145 RELEASE_ASSERT(length
<= std::numeric_limits
<unsigned>::max());
146 document()->cachedImage()->appendData(data
, length
);
152 // Make sure the image layoutObject gets created because we need the layoutObject
153 // to read the aspect ratio. See crbug.com/320244
154 document()->updateLayoutTreeIfNeeded();
155 document()->imageUpdated();
158 void ImageDocumentParser::finish()
160 if (!isStopped() && document()->imageElement() && document()->cachedImage()) {
161 ImageResource
* cachedImage
= document()->cachedImage();
162 cachedImage
->finish();
163 cachedImage
->setResponse(document()->frame()->loader().documentLoader()->response());
165 // Report the natural image size in the page title, regardless of zoom level.
166 // At a zoom level of 1 the image is guaranteed to have an integer size.
167 IntSize size
= flooredIntSize(cachedImage
->imageSizeForLayoutObject(document()->imageElement()->layoutObject(), 1.0f
));
169 // Compute the title, we use the decoded filename of the resource, falling
170 // back on the (decoded) hostname if there is no path.
171 String fileName
= decodeURLEscapeSequences(document()->url().lastPathComponent());
172 if (fileName
.isEmpty())
173 fileName
= document()->url().host();
174 document()->setTitle(imageTitle(fileName
, size
));
177 document()->imageUpdated();
181 document()->finishedParsing();
186 ImageDocument::ImageDocument(const DocumentInit
& initializer
)
187 : HTMLDocument(initializer
, ImageDocumentClass
)
188 , m_imageElement(nullptr)
189 , m_imageSizeIsKnown(false)
190 , m_didShrinkImage(false)
191 , m_shouldShrinkImage(shouldShrinkToFit())
192 , m_shrinkToFitMode(frame()->settings()->viewportEnabled() ? Viewport
: Desktop
)
194 setCompatibilityMode(QuirksMode
);
195 lockCompatibilityMode();
198 PassRefPtrWillBeRawPtr
<DocumentParser
> ImageDocument::createParser()
200 return ImageDocumentParser::create(this);
203 void ImageDocument::createDocumentStructure(bool loadingMultipartContent
)
205 RefPtrWillBeRawPtr
<HTMLHtmlElement
> rootElement
= HTMLHtmlElement::create(*this);
206 appendChild(rootElement
);
207 rootElement
->insertedByParser();
210 frame()->loader().dispatchDocumentElementAvailable();
211 // Normally, ImageDocument creates an HTMLImageElement that doesn't actually load
212 // anything, and the ImageDocument routes the main resource data into the HTMLImageElement's
213 // ImageResource. However, the main resource pipeline doesn't know how to handle multipart content.
214 // For multipart content, we instead stop streaming data through the main resource and re-request
215 // the data directly.
216 if (loadingMultipartContent
)
217 loader()->stopLoading();
219 RefPtrWillBeRawPtr
<HTMLHeadElement
> head
= HTMLHeadElement::create(*this);
220 RefPtrWillBeRawPtr
<HTMLMetaElement
> meta
= HTMLMetaElement::create(*this);
221 meta
->setAttribute(nameAttr
, "viewport");
222 meta
->setAttribute(contentAttr
, "width=device-width, minimum-scale=0.1");
223 head
->appendChild(meta
);
225 RefPtrWillBeRawPtr
<HTMLBodyElement
> body
= HTMLBodyElement::create(*this);
226 body
->setAttribute(styleAttr
, "margin: 0px;");
228 m_imageElement
= HTMLImageElement::create(*this);
229 m_imageElement
->setAttribute(styleAttr
, "-webkit-user-select: none");
230 // If the image is multipart, we neglect to mention to the HTMLImageElement that it's in an
231 // ImageDocument, so that it requests the image normally.
232 if (!loadingMultipartContent
)
233 m_imageElement
->setLoadingImageDocument();
234 m_imageElement
->setSrc(url().string());
235 body
->appendChild(m_imageElement
.get());
237 if (shouldShrinkToFit()) {
238 // Add event listeners
239 RefPtrWillBeRawPtr
<EventListener
> listener
= ImageEventListener::create(this);
240 if (LocalDOMWindow
* domWindow
= this->domWindow())
241 domWindow
->addEventListener("resize", listener
, false);
242 if (m_shrinkToFitMode
== Desktop
)
243 m_imageElement
->addEventListener("click", listener
.release(), false);
246 rootElement
->appendChild(head
);
247 rootElement
->appendChild(body
);
248 if (loadingMultipartContent
)
252 float ImageDocument::scale() const
254 if (!m_imageElement
|| m_imageElement
->document() != this)
257 FrameView
* view
= frame()->view();
261 ASSERT(m_imageElement
->cachedImage());
262 LayoutSize imageSize
= m_imageElement
->cachedImage()->imageSizeForLayoutObject(m_imageElement
->layoutObject(), pageZoomFactor(this));
263 LayoutSize windowSize
= LayoutSize(view
->width(), view
->height());
265 float widthScale
= windowSize
.width().toFloat() / imageSize
.width().toFloat();
266 float heightScale
= windowSize
.height().toFloat() / imageSize
.height().toFloat();
268 return min(widthScale
, heightScale
);
271 void ImageDocument::resizeImageToFit(ScaleType type
)
273 if (!m_imageElement
|| m_imageElement
->document() != this || (pageZoomFactor(this) > 1 && type
== ScaleOnlyUnzoomedDocument
))
276 ASSERT(m_imageElement
->cachedImage());
277 LayoutSize imageSize
= m_imageElement
->cachedImage()->imageSizeForLayoutObject(m_imageElement
->layoutObject(), pageZoomFactor(this));
279 float scale
= this->scale();
280 m_imageElement
->setWidth(static_cast<int>(imageSize
.width() * scale
));
281 m_imageElement
->setHeight(static_cast<int>(imageSize
.height() * scale
));
283 m_imageElement
->setInlineStyleProperty(CSSPropertyCursor
, CSSValueZoomIn
);
286 void ImageDocument::imageClicked(int x
, int y
)
288 ASSERT(m_shrinkToFitMode
== Desktop
);
290 if (!m_imageSizeIsKnown
|| imageFitsInWindow())
293 m_shouldShrinkImage
= !m_shouldShrinkImage
;
295 if (m_shouldShrinkImage
) {
296 windowSizeChanged(ScaleZoomedDocument
);
298 restoreImageSize(ScaleZoomedDocument
);
302 double scale
= this->scale();
304 double scrollX
= x
/ scale
- static_cast<double>(frame()->view()->width()) / 2;
305 double scrollY
= y
/ scale
- static_cast<double>(frame()->view()->height()) / 2;
307 frame()->view()->setScrollPosition(DoublePoint(scrollX
, scrollY
), ProgrammaticScroll
);
311 void ImageDocument::imageUpdated()
313 ASSERT(m_imageElement
);
315 if (m_imageSizeIsKnown
)
318 if (!m_imageElement
->cachedImage() || m_imageElement
->cachedImage()->imageSizeForLayoutObject(m_imageElement
->layoutObject(), pageZoomFactor(this)).isEmpty())
321 m_imageSizeIsKnown
= true;
323 if (shouldShrinkToFit()) {
324 // Force resizing of the image
325 windowSizeChanged(ScaleOnlyUnzoomedDocument
);
328 // Update layout as soon as image size is known. This enables large image files to render progressively or to animate.
332 void ImageDocument::restoreImageSize(ScaleType type
)
334 ASSERT(m_shrinkToFitMode
== Desktop
);
336 if (!m_imageElement
|| !m_imageSizeIsKnown
|| m_imageElement
->document() != this || (pageZoomFactor(this) < 1 && type
== ScaleOnlyUnzoomedDocument
))
339 ASSERT(m_imageElement
->cachedImage());
340 LayoutSize imageSize
= m_imageElement
->cachedImage()->imageSizeForLayoutObject(m_imageElement
->layoutObject(), 1.0f
);
341 m_imageElement
->setWidth(imageSize
.width());
342 m_imageElement
->setHeight(imageSize
.height());
344 if (imageFitsInWindow())
345 m_imageElement
->removeInlineStyleProperty(CSSPropertyCursor
);
347 m_imageElement
->setInlineStyleProperty(CSSPropertyCursor
, CSSValueZoomOut
);
349 m_didShrinkImage
= false;
352 bool ImageDocument::imageFitsInWindow() const
354 ASSERT(m_shrinkToFitMode
== Desktop
);
356 if (!m_imageElement
|| m_imageElement
->document() != this)
359 FrameView
* view
= frame()->view();
363 ASSERT(m_imageElement
->cachedImage());
364 LayoutSize imageSize
= m_imageElement
->cachedImage()->imageSizeForLayoutObject(m_imageElement
->layoutObject(), pageZoomFactor(this));
365 LayoutSize windowSize
= LayoutSize(view
->width(), view
->height());
367 return imageSize
.width() <= windowSize
.width() && imageSize
.height() <= windowSize
.height();
370 void ImageDocument::windowSizeChanged(ScaleType type
)
372 if (!m_imageElement
|| !m_imageSizeIsKnown
|| m_imageElement
->document() != this)
375 if (m_shrinkToFitMode
== Viewport
) {
376 // For huge images, minimum-scale=0.1 is still too big on small screens.
377 // Set max-width so that the image will shrink to fit the width of the screen when
378 // the scale is minimum.
379 // Don't shrink height to fit because we use width=device-width in viewport meta tag,
380 // and expect a full-width reading mode for normal-width-huge-height images.
381 int viewportWidth
= frame()->host()->visualViewport().size().width();
382 m_imageElement
->setInlineStyleProperty(CSSPropertyMaxWidth
, viewportWidth
* 10, CSSPrimitiveValue::UnitType::Pixels
);
386 bool fitsInWindow
= imageFitsInWindow();
388 // If the image has been explicitly zoomed in, restore the cursor if the image fits
389 // and set it to a zoom out cursor if the image doesn't fit
390 if (!m_shouldShrinkImage
) {
392 m_imageElement
->removeInlineStyleProperty(CSSPropertyCursor
);
394 m_imageElement
->setInlineStyleProperty(CSSPropertyCursor
, CSSValueZoomOut
);
398 if (m_didShrinkImage
) {
399 // If the window has been resized so that the image fits, restore the image size
400 // otherwise update the restored image size.
402 restoreImageSize(type
);
404 resizeImageToFit(type
);
406 // If the image isn't resized but needs to be, then resize it.
408 resizeImageToFit(type
);
409 m_didShrinkImage
= true;
414 ImageResource
* ImageDocument::cachedImage()
416 bool loadingMultipartContent
= loader() && loader()->loadingMultipartContent();
418 createDocumentStructure(loadingMultipartContent
);
420 return loadingMultipartContent
? nullptr : m_imageElement
->cachedImage();
423 bool ImageDocument::shouldShrinkToFit() const
425 return frame()->isMainFrame();
429 void ImageDocument::dispose()
431 m_imageElement
= nullptr;
432 HTMLDocument::dispose();
436 DEFINE_TRACE(ImageDocument
)
438 visitor
->trace(m_imageElement
);
439 HTMLDocument::trace(visitor
);
444 void ImageEventListener::handleEvent(ExecutionContext
*, Event
* event
)
446 if (event
->type() == EventTypeNames::resize
) {
447 m_doc
->windowSizeChanged(ImageDocument::ScaleOnlyUnzoomedDocument
);
448 } else if (event
->type() == EventTypeNames::click
&& event
->isMouseEvent()) {
449 MouseEvent
* mouseEvent
= toMouseEvent(event
);
450 m_doc
->imageClicked(mouseEvent
->x(), mouseEvent
->y());
454 bool ImageEventListener::operator==(const EventListener
& listener
)
456 if (const ImageEventListener
* imageEventListener
= ImageEventListener::cast(&listener
))
457 return m_doc
== imageEventListener
->m_doc
;