1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2000 Dirk Mueller (mueller@kde.org)
5 *           (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
6 *           (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
7 * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
8 * Copyright (C) 2010 Google Inc. All rights reserved.
9 * Copyright (C) Research In Motion Limited 2011-2012. All rights reserved.
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 * Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB.  If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
25 *
26 */
27
28#include "config.h"
29#include "core/rendering/RenderImage.h"
30
31#include "core/HTMLNames.h"
32#include "core/editing/FrameSelection.h"
33#include "core/fetch/ImageResource.h"
34#include "core/fetch/ResourceLoadPriorityOptimizer.h"
35#include "core/fetch/ResourceLoader.h"
36#include "core/frame/LocalFrame.h"
37#include "core/html/HTMLAreaElement.h"
38#include "core/html/HTMLImageElement.h"
39#include "core/html/HTMLInputElement.h"
40#include "core/html/HTMLMapElement.h"
41#include "core/paint/ImagePainter.h"
42#include "core/rendering/HitTestResult.h"
43#include "core/rendering/PaintInfo.h"
44#include "core/rendering/RenderLayer.h"
45#include "core/rendering/RenderView.h"
46#include "core/rendering/TextRunConstructor.h"
47#include "core/svg/graphics/SVGImage.h"
48#include "platform/fonts/Font.h"
49#include "platform/fonts/FontCache.h"
50
51namespace blink {
52
53float deviceScaleFactor(LocalFrame*);
54
55using namespace HTMLNames;
56
57RenderImage::RenderImage(Element* element)
58    : RenderReplaced(element, IntSize())
59    , m_didIncrementVisuallyNonEmptyPixelCount(false)
60    , m_isGeneratedContent(false)
61    , m_imageDevicePixelRatio(1.0f)
62{
63    updateAltText();
64    ResourceLoadPriorityOptimizer::resourceLoadPriorityOptimizer()->addRenderObject(this);
65}
66
67RenderImage* RenderImage::createAnonymous(Document* document)
68{
69    RenderImage* image = new RenderImage(0);
70    image->setDocumentForAnonymous(document);
71    return image;
72}
73
74RenderImage::~RenderImage()
75{
76}
77
78void RenderImage::destroy()
79{
80    ASSERT(m_imageResource);
81    m_imageResource->shutdown();
82    RenderReplaced::destroy();
83}
84
85void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
86{
87    ASSERT(!m_imageResource);
88    m_imageResource = imageResource;
89    m_imageResource->initialize(this);
90}
91
92// Alt text is restricted to this maximum size, in pixels.  These are
93// signed integers because they are compared with other signed values.
94static const float maxAltTextWidth = 1024;
95static const int maxAltTextHeight = 256;
96
97IntSize RenderImage::imageSizeForError(ImageResource* newImage) const
98{
99    ASSERT_ARG(newImage, newImage);
100    ASSERT_ARG(newImage, newImage->imageForRenderer(this));
101
102    IntSize imageSize;
103    if (newImage->willPaintBrokenImage()) {
104        float deviceScaleFactor = blink::deviceScaleFactor(frame());
105        pair<Image*, float> brokenImageAndImageScaleFactor = ImageResource::brokenImage(deviceScaleFactor);
106        imageSize = brokenImageAndImageScaleFactor.first->size();
107        imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
108    } else
109        imageSize = newImage->imageForRenderer(this)->size();
110
111    // imageSize() returns 0 for the error image. We need the true size of the
112    // error image, so we have to get it by grabbing image() directly.
113    return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
114}
115
116// Sets the image height and width to fit the alt text.  Returns true if the
117// image size changed.
118bool RenderImage::setImageSizeForAltText(ImageResource* newImage /* = 0 */)
119{
120    IntSize imageSize;
121    if (newImage && newImage->imageForRenderer(this))
122        imageSize = imageSizeForError(newImage);
123    else if (!m_altText.isEmpty() || newImage) {
124        // If we'll be displaying either text or an image, add a little padding.
125        imageSize = IntSize(paddingWidth, paddingHeight);
126    }
127
128    // we have an alt and the user meant it (its not a text we invented)
129    if (!m_altText.isEmpty()) {
130        FontCachePurgePreventer fontCachePurgePreventer;
131
132        const Font& font = style()->font();
133        IntSize paddedTextSize(paddingWidth + std::min(ceilf(font.width(constructTextRun(this, font, m_altText, style()))), maxAltTextWidth), paddingHeight + std::min(font.fontMetrics().height(), maxAltTextHeight));
134        imageSize = imageSize.expandedTo(paddedTextSize);
135    }
136
137    if (imageSize == intrinsicSize())
138        return false;
139
140    setIntrinsicSize(imageSize);
141    return true;
142}
143
144void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
145{
146    if (documentBeingDestroyed())
147        return;
148
149    if (hasBoxDecorationBackground() || hasMask() || hasShapeOutside())
150        RenderReplaced::imageChanged(newImage, rect);
151
152    if (!m_imageResource)
153        return;
154
155    if (newImage != m_imageResource->imagePtr())
156        return;
157
158    // Per the spec, we let the server-sent header override srcset/other sources of dpr.
159    // https://github.com/igrigorik/http-client-hints/blob/master/draft-grigorik-http-client-hints-01.txt#L255
160    if (m_imageResource->cachedImage() && m_imageResource->cachedImage()->hasDevicePixelRatioHeaderValue())
161        m_imageDevicePixelRatio = 1 / m_imageResource->cachedImage()->devicePixelRatioHeaderValue();
162
163    if (!m_didIncrementVisuallyNonEmptyPixelCount) {
164        // At a zoom level of 1 the image is guaranteed to have an integer size.
165        view()->frameView()->incrementVisuallyNonEmptyPixelCount(flooredIntSize(m_imageResource->imageSize(1.0f)));
166        m_didIncrementVisuallyNonEmptyPixelCount = true;
167    }
168
169    bool imageSizeChanged = false;
170
171    // Set image dimensions, taking into account the size of the alt text.
172    if (m_imageResource->errorOccurred() || !newImage)
173        imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
174
175    paintInvalidationOrMarkForLayout(imageSizeChanged, rect);
176}
177
178void RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize)
179{
180    if (m_imageResource->errorOccurred() || !m_imageResource->hasImage())
181        return;
182    setIntrinsicSize(newSize);
183}
184
185void RenderImage::updateInnerContentRect()
186{
187    // Propagate container size to the image resource.
188    LayoutRect containerRect = replacedContentRect();
189    IntSize containerSize(containerRect.width(), containerRect.height());
190    if (!containerSize.isEmpty())
191        m_imageResource->setContainerSizeForRenderer(containerSize);
192}
193
194void RenderImage::paintInvalidationOrMarkForLayout(bool imageSizeChangedToAccomodateAltText, const IntRect* rect)
195{
196    LayoutSize oldIntrinsicSize = intrinsicSize();
197    LayoutSize newIntrinsicSize = m_imageResource->intrinsicSize(style()->effectiveZoom());
198    updateIntrinsicSizeIfNeeded(newIntrinsicSize);
199
200    // In the case of generated image content using :before/:after/content, we might not be
201    // in the render tree yet. In that case, we just need to update our intrinsic size.
202    // layout() will be called after we are inserted in the tree which will take care of
203    // what we are doing here.
204    if (!containingBlock())
205        return;
206
207    bool imageSourceHasChangedSize = oldIntrinsicSize != newIntrinsicSize || imageSizeChangedToAccomodateAltText;
208    if (imageSourceHasChangedSize)
209        setPreferredLogicalWidthsDirty();
210
211    // If the actual area occupied by the image has changed and it is not constrained by style then a layout is required.
212    bool imageSizeIsConstrained = style()->logicalWidth().isSpecified() && style()->logicalHeight().isSpecified();
213
214    // FIXME: We only need to recompute the containing block's preferred size if the containing block's size
215    // depends on the image's size (i.e., the container uses shrink-to-fit sizing).
216    // There's no easy way to detect that shrink-to-fit is needed, always force a layout.
217    bool containingBlockNeedsToRecomputePreferredSize = style()->logicalWidth().isPercent() || style()->logicalMaxWidth().isPercent()  || style()->logicalMinWidth().isPercent();
218
219    if (imageSourceHasChangedSize && (!imageSizeIsConstrained || containingBlockNeedsToRecomputePreferredSize)) {
220        setNeedsLayoutAndFullPaintInvalidation();
221        return;
222    }
223
224    // The image hasn't changed in size or its style constrains its size, so a paint invalidation will suffice.
225    if (everHadLayout() && !selfNeedsLayout()) {
226        // The inner content rectangle is calculated during layout, but may need an update now
227        // (unless the box has already been scheduled for layout). In order to calculate it, we
228        // may need values from the containing block, though, so make sure that we're not too
229        // early. It may be that layout hasn't even taken place once yet.
230        updateInnerContentRect();
231    }
232
233    LayoutRect paintInvalidationRect;
234    if (rect) {
235        // The image changed rect is in source image coordinates (without zoom),
236        // so map from the bounds of the image to the contentsBox.
237        const LayoutSize imageSizeWithoutZoom = m_imageResource->imageSize(1 / style()->effectiveZoom());
238        paintInvalidationRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), imageSizeWithoutZoom), contentBoxRect()));
239        // Guard against too-large changed rects.
240        paintInvalidationRect.intersect(contentBoxRect());
241    } else {
242        paintInvalidationRect = contentBoxRect();
243    }
244
245    {
246        // FIXME: We should not be allowing paint invalidations during layout. crbug.com/339584
247        AllowPaintInvalidationScope scoper(frameView());
248        DisableCompositingQueryAsserts disabler;
249        invalidatePaintRectangle(paintInvalidationRect);
250    }
251
252    // Tell any potential compositing layers that the image needs updating.
253    contentChanged(ImageChanged);
254}
255
256void RenderImage::notifyFinished(Resource* newImage)
257{
258    if (!m_imageResource)
259        return;
260
261    if (documentBeingDestroyed())
262        return;
263
264    invalidateBackgroundObscurationStatus();
265
266    if (newImage == m_imageResource->cachedImage()) {
267        // tell any potential compositing layers
268        // that the image is done and they can reference it directly.
269        contentChanged(ImageChanged);
270    }
271}
272
273void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
274{
275    ImagePainter(*this).paintReplaced(paintInfo, paintOffset);
276}
277
278void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
279{
280    ImagePainter(*this).paint(paintInfo, paintOffset);
281}
282
283void RenderImage::areaElementFocusChanged(HTMLAreaElement* areaElement)
284{
285    ASSERT(areaElement->imageElement() == node());
286
287    Path path = areaElement->computePath(this);
288    if (path.isEmpty())
289        return;
290
291    RenderStyle* areaElementStyle = areaElement->computedStyle();
292    unsigned short outlineWidth = areaElementStyle->outlineWidth();
293
294    IntRect paintInvalidationRect = enclosingIntRect(path.boundingRect());
295    paintInvalidationRect.moveBy(-absoluteContentBox().location());
296    paintInvalidationRect.inflate(outlineWidth);
297
298    paintInvalidationOrMarkForLayout(false, &paintInvalidationRect);
299}
300
301bool RenderImage::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox*) const
302{
303    if (!RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(bleedAvoidance))
304        return false;
305
306    return !const_cast<RenderImage*>(this)->boxDecorationBackgroundIsKnownToBeObscured();
307}
308
309bool RenderImage::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned) const
310{
311    if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
312        return false;
313    if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
314        return false;
315    if (!contentBoxRect().contains(localRect))
316        return false;
317    EFillBox backgroundClip = style()->backgroundClip();
318    // Background paints under borders.
319    if (backgroundClip == BorderFillBox && style()->hasBorder() && !style()->borderObscuresBackground())
320        return false;
321    // Background shows in padding area.
322    if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
323        return false;
324    // Object-position may leave parts of the content box empty, regardless of the value of object-fit.
325    if (style()->objectPosition() != RenderStyle::initialObjectPosition())
326        return false;
327    // Object-fit may leave parts of the content box empty.
328    ObjectFit objectFit = style()->objectFit();
329    if (objectFit != ObjectFitFill && objectFit != ObjectFitCover)
330        return false;
331    // Check for image with alpha.
332    return m_imageResource->cachedImage() && m_imageResource->cachedImage()->currentFrameKnownToBeOpaque(this);
333}
334
335bool RenderImage::computeBackgroundIsKnownToBeObscured()
336{
337    if (!hasBackground())
338        return false;
339
340    LayoutRect paintedExtent;
341    if (!getBackgroundPaintedExtent(paintedExtent))
342        return false;
343    return foregroundIsKnownToBeOpaqueInRect(paintedExtent, 0);
344}
345
346LayoutUnit RenderImage::minimumReplacedHeight() const
347{
348    return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
349}
350
351HTMLMapElement* RenderImage::imageMap() const
352{
353    HTMLImageElement* i = isHTMLImageElement(node()) ? toHTMLImageElement(node()) : 0;
354    return i ? i->treeScope().getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
355}
356
357bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
358{
359    HitTestResult tempResult(result.hitTestLocation());
360    bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction);
361
362    if (tempResult.innerNode() && node()) {
363        if (HTMLMapElement* map = imageMap()) {
364            LayoutRect contentBox = contentBoxRect();
365            float scaleFactor = 1 / style()->effectiveZoom();
366            LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location());
367            mapLocation.scale(scaleFactor, scaleFactor);
368
369            if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
370                tempResult.setInnerNonSharedNode(node());
371        }
372    }
373
374    if (!inside && result.isRectBasedTest())
375        result.append(tempResult);
376    if (inside)
377        result = tempResult;
378    return inside;
379}
380
381void RenderImage::updateAltText()
382{
383    if (!node())
384        return;
385
386    if (isHTMLInputElement(*node()))
387        m_altText = toHTMLInputElement(node())->altText();
388    else if (isHTMLImageElement(*node()))
389        m_altText = toHTMLImageElement(node())->altText();
390}
391
392void RenderImage::layout()
393{
394    RenderReplaced::layout();
395    updateInnerContentRect();
396}
397
398bool RenderImage::updateImageLoadingPriorities()
399{
400    if (!m_imageResource || !m_imageResource->cachedImage() || m_imageResource->cachedImage()->isLoaded())
401        return false;
402
403    LayoutRect viewBounds = viewRect();
404    LayoutRect objectBounds = absoluteContentBox();
405
406    // The object bounds might be empty right now, so intersects will fail since it doesn't deal
407    // with empty rects. Use LayoutRect::contains in that case.
408    bool isVisible;
409    if (!objectBounds.isEmpty())
410        isVisible =  viewBounds.intersects(objectBounds);
411    else
412        isVisible = viewBounds.contains(objectBounds);
413
414    ResourceLoadPriorityOptimizer::VisibilityStatus status = isVisible ?
415        ResourceLoadPriorityOptimizer::Visible : ResourceLoadPriorityOptimizer::NotVisible;
416
417    LayoutRect screenArea;
418    if (!objectBounds.isEmpty()) {
419        screenArea = viewBounds;
420        screenArea.intersect(objectBounds);
421    }
422
423    ResourceLoadPriorityOptimizer::resourceLoadPriorityOptimizer()->notifyImageResourceVisibility(m_imageResource->cachedImage(), status, screenArea);
424
425    return true;
426}
427
428void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const
429{
430    RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio);
431
432    // Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use.
433    if (intrinsicSize.isEmpty() && (m_imageResource->imageHasRelativeWidth() || m_imageResource->imageHasRelativeHeight())) {
434        RenderObject* containingBlock = isOutOfFlowPositioned() ? container() : this->containingBlock();
435        if (containingBlock->isBox()) {
436            RenderBox* box = toRenderBox(containingBlock);
437            intrinsicSize.setWidth(box->availableLogicalWidth().toFloat());
438            intrinsicSize.setHeight(box->availableLogicalHeight(IncludeMarginBorderPadding).toFloat());
439        }
440    }
441    // Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image.
442    // Video is excluded from this behavior because video elements have a default aspect ratio that a failed poster image load should not override.
443    if (m_imageResource && m_imageResource->errorOccurred() && !isVideo()) {
444        intrinsicRatio = 1;
445        return;
446    }
447}
448
449bool RenderImage::needsPreferredWidthsRecalculation() const
450{
451    if (RenderReplaced::needsPreferredWidthsRecalculation())
452        return true;
453    return embeddedContentBox();
454}
455
456RenderBox* RenderImage::embeddedContentBox() const
457{
458    if (!m_imageResource)
459        return 0;
460
461    ImageResource* cachedImage = m_imageResource->cachedImage();
462    if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
463        return toSVGImage(cachedImage->image())->embeddedContentBox();
464
465    return 0;
466}
467
468} // namespace blink
469