Keep auxilliary media objects on the heap always.
[chromium-blink-merge.git] / third_party / WebKit / Source / core / html / ImageDocument.cpp
blob631409499fcda8b1e3ed845c6c8c2de6d9000840
1 /*
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
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.
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.
25 #include "config.h"
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"
49 #include <limits>
51 using namespace std;
53 namespace blink {
55 using namespace HTMLNames;
57 class ImageEventListener : public EventListener {
58 public:
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)
67 : 0;
70 virtual bool operator==(const EventListener& other);
72 DEFINE_INLINE_VIRTUAL_TRACE()
74 visitor->trace(m_doc);
75 EventListener::trace(visitor);
78 private:
79 ImageEventListener(ImageDocument* document)
80 : EventListener(ImageEventListenerType)
81 , m_doc(document)
85 virtual void handleEvent(ExecutionContext*, Event*);
87 RawPtrWillBeMember<ImageDocument> m_doc;
90 class ImageDocumentParser : public RawDataDocumentParser {
91 public:
92 static PassRefPtrWillBeRawPtr<ImageDocumentParser> create(ImageDocument* document)
94 return adoptRefWillBeNoop(new ImageDocumentParser(document));
97 ImageDocument* document() const
99 return toImageDocument(RawDataDocumentParser::document());
102 private:
103 ImageDocumentParser(ImageDocument* document)
104 : RawDataDocumentParser(document)
108 void appendBytes(const char*, size_t) override;
109 virtual void finish();
112 // --------
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());
130 result.append(')');
131 return result.toString();
134 void ImageDocumentParser::appendBytes(const char* data, size_t length)
136 if (!length)
137 return;
139 LocalFrame* frame = document()->frame();
140 Settings* settings = frame->settings();
141 if (!frame->loader().client()->allowImage(!settings || settings->imagesEnabled(), document()->url()))
142 return;
144 if (document()->cachedImage()) {
145 RELEASE_ASSERT(length <= std::numeric_limits<unsigned>::max());
146 document()->cachedImage()->appendData(data, length);
149 if (!document())
150 return;
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));
168 if (size.width()) {
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();
180 if (document())
181 document()->finishedParsing();
184 // --------
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();
209 if (frame())
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)
249 finishedParsing();
252 float ImageDocument::scale() const
254 if (!m_imageElement || m_imageElement->document() != this)
255 return 1.0f;
257 FrameView* view = frame()->view();
258 if (!view)
259 return 1;
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))
274 return;
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())
291 return;
293 m_shouldShrinkImage = !m_shouldShrinkImage;
295 if (m_shouldShrinkImage) {
296 windowSizeChanged(ScaleZoomedDocument);
297 } else {
298 restoreImageSize(ScaleZoomedDocument);
300 updateLayout();
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)
316 return;
318 if (!m_imageElement->cachedImage() || m_imageElement->cachedImage()->imageSizeForLayoutObject(m_imageElement->layoutObject(), pageZoomFactor(this)).isEmpty())
319 return;
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.
329 updateLayout();
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))
337 return;
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);
346 else
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)
357 return true;
359 FrameView* view = frame()->view();
360 if (!view)
361 return true;
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)
373 return;
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);
383 return;
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) {
391 if (fitsInWindow)
392 m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
393 else
394 m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomOut);
395 return;
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.
401 if (fitsInWindow)
402 restoreImageSize(type);
403 else
404 resizeImageToFit(type);
405 } else {
406 // If the image isn't resized but needs to be, then resize it.
407 if (!fitsInWindow) {
408 resizeImageToFit(type);
409 m_didShrinkImage = true;
414 ImageResource* ImageDocument::cachedImage()
416 bool loadingMultipartContent = loader() && loader()->loadingMultipartContent();
417 if (!m_imageElement)
418 createDocumentStructure(loadingMultipartContent);
420 return loadingMultipartContent ? nullptr : m_imageElement->cachedImage();
423 bool ImageDocument::shouldShrinkToFit() const
425 return frame()->isMainFrame();
428 #if !ENABLE(OILPAN)
429 void ImageDocument::dispose()
431 m_imageElement = nullptr;
432 HTMLDocument::dispose();
434 #endif
436 DEFINE_TRACE(ImageDocument)
438 visitor->trace(m_imageElement);
439 HTMLDocument::trace(visitor);
442 // --------
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;
458 return false;