Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2000 Dirk Mueller (mueller (at) kde.org)
      5  *           (C) 2006 Allan Sandfeld Jensen (kde (at) carewolf.com)
      6  *           (C) 2006 Samuel Weinig (sam.weinig (at) 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 
     51 namespace blink {
     52 
     53 float deviceScaleFactor(LocalFrame*);
     54 
     55 using namespace HTMLNames;
     56 
     57 RenderImage::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 
     67 RenderImage* RenderImage::createAnonymous(Document* document)
     68 {
     69     RenderImage* image = new RenderImage(0);
     70     image->setDocumentForAnonymous(document);
     71     return image;
     72 }
     73 
     74 RenderImage::~RenderImage()
     75 {
     76 }
     77 
     78 void RenderImage::destroy()
     79 {
     80     ASSERT(m_imageResource);
     81     m_imageResource->shutdown();
     82     RenderReplaced::destroy();
     83 }
     84 
     85 void 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.
     94 static const float maxAltTextWidth = 1024;
     95 static const int maxAltTextHeight = 256;
     96 
     97 IntSize 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.
    118 bool 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 
    144 void 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 
    178 void RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize)
    179 {
    180     if (m_imageResource->errorOccurred() || !m_imageResource->hasImage())
    181         return;
    182     setIntrinsicSize(newSize);
    183 }
    184 
    185 void 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 
    194 void 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 
    256 void 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 
    273 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    274 {
    275     ImagePainter(*this).paintReplaced(paintInfo, paintOffset);
    276 }
    277 
    278 void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    279 {
    280     ImagePainter(*this).paint(paintInfo, paintOffset);
    281 }
    282 
    283 void 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 
    301 bool 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 
    309 bool 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 
    335 bool 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 
    346 LayoutUnit RenderImage::minimumReplacedHeight() const
    347 {
    348     return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
    349 }
    350 
    351 HTMLMapElement* RenderImage::imageMap() const
    352 {
    353     HTMLImageElement* i = isHTMLImageElement(node()) ? toHTMLImageElement(node()) : 0;
    354     return i ? i->treeScope().getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
    355 }
    356 
    357 bool 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 
    381 void 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 
    392 void RenderImage::layout()
    393 {
    394     RenderReplaced::layout();
    395     updateInnerContentRect();
    396 }
    397 
    398 bool 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 
    428 void 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 
    449 bool RenderImage::needsPreferredWidthsRecalculation() const
    450 {
    451     if (RenderReplaced::needsPreferredWidthsRecalculation())
    452         return true;
    453     return embeddedContentBox();
    454 }
    455 
    456 RenderBox* 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