• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
51 namespace blink {
52 
53 float deviceScaleFactor(LocalFrame*);
54 
55 using namespace HTMLNames;
56 
RenderImage(Element * element)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 
createAnonymous(Document * document)67 RenderImage* RenderImage::createAnonymous(Document* document)
68 {
69     RenderImage* image = new RenderImage(0);
70     image->setDocumentForAnonymous(document);
71     return image;
72 }
73 
~RenderImage()74 RenderImage::~RenderImage()
75 {
76 }
77 
destroy()78 void RenderImage::destroy()
79 {
80     ASSERT(m_imageResource);
81     m_imageResource->shutdown();
82     RenderReplaced::destroy();
83 }
84 
setImageResource(PassOwnPtr<RenderImageResource> imageResource)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 
imageSizeForError(ImageResource * newImage) const97 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.
setImageSizeForAltText(ImageResource * newImage)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 
imageChanged(WrappedImagePtr newImage,const IntRect * rect)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 
updateIntrinsicSizeIfNeeded(const LayoutSize & newSize)178 void RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize)
179 {
180     if (m_imageResource->errorOccurred() || !m_imageResource->hasImage())
181         return;
182     setIntrinsicSize(newSize);
183 }
184 
updateInnerContentRect()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 
paintInvalidationOrMarkForLayout(bool imageSizeChangedToAccomodateAltText,const IntRect * rect)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 
notifyFinished(Resource * newImage)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 
paintReplaced(PaintInfo & paintInfo,const LayoutPoint & paintOffset)273 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
274 {
275     ImagePainter(*this).paintReplaced(paintInfo, paintOffset);
276 }
277 
paint(PaintInfo & paintInfo,const LayoutPoint & paintOffset)278 void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
279 {
280     ImagePainter(*this).paint(paintInfo, paintOffset);
281 }
282 
areaElementFocusChanged(HTMLAreaElement * areaElement)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 
boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance,InlineFlowBox *) const301 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 
foregroundIsKnownToBeOpaqueInRect(const LayoutRect & localRect,unsigned) const309 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 
computeBackgroundIsKnownToBeObscured()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 
minimumReplacedHeight() const346 LayoutUnit RenderImage::minimumReplacedHeight() const
347 {
348     return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
349 }
350 
imageMap() const351 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 
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,const HitTestLocation & locationInContainer,const LayoutPoint & accumulatedOffset,HitTestAction hitTestAction)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 
updateAltText()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 
layout()392 void RenderImage::layout()
393 {
394     RenderReplaced::layout();
395     updateInnerContentRect();
396 }
397 
updateImageLoadingPriorities()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 
computeIntrinsicRatioInformation(FloatSize & intrinsicSize,double & intrinsicRatio) const428 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 
needsPreferredWidthsRecalculation() const449 bool RenderImage::needsPreferredWidthsRecalculation() const
450 {
451     if (RenderReplaced::needsPreferredWidthsRecalculation())
452         return true;
453     return embeddedContentBox();
454 }
455 
embeddedContentBox() const456 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