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 "RenderImage.h"
30
31#include "BitmapImage.h"
32#include "CachedImage.h"
33#include "Font.h"
34#include "FontCache.h"
35#include "Frame.h"
36#include "FrameSelection.h"
37#include "GeometryUtilities.h"
38#include "GraphicsContext.h"
39#include "HTMLAreaElement.h"
40#include "HTMLImageElement.h"
41#include "HTMLInputElement.h"
42#include "HTMLMapElement.h"
43#include "HTMLNames.h"
44#include "HitTestResult.h"
45#include "InlineElementBox.h"
46#include "Page.h"
47#include "PaintInfo.h"
48#include "RenderFlowThread.h"
49#include "RenderImageResourceStyleImage.h"
50#include "RenderView.h"
51#include "SVGImage.h"
52#include <wtf/StackStats.h>
53
54#if PLATFORM(IOS)
55#include "LogicalSelectionOffsetCaches.h"
56#include "SelectionRect.h"
57#endif
58
59namespace WebCore {
60
61#if PLATFORM(IOS)
62// FIXME: This doesn't behave correctly for floating or positioned images, but WebCore doesn't handle those well
63// during selection creation yet anyway.
64// FIXME: We can't tell whether or not we contain the start or end of the selected Range using only the offsets
65// of the start and end, we need to know the whole Position.
66void RenderImage::collectSelectionRects(Vector<SelectionRect>& rects, unsigned, unsigned)
67{
68    RenderBlock* containingBlock = this->containingBlock();
69
70    IntRect imageRect;
71    // FIXME: It doesn't make sense to package line bounds into SelectionRects. We should find
72    // the right and left extent of the selection once for the entire selected Range, perhaps
73    // using the Range's common ancestor.
74    IntRect lineExtentRect;
75    bool isFirstOnLine = false;
76    bool isLastOnLine = false;
77
78    InlineBox* inlineBox = inlineBoxWrapper();
79    if (!inlineBox) {
80        // This is a block image.
81        imageRect = IntRect(0, 0, width(), height());
82        isFirstOnLine = true;
83        isLastOnLine = true;
84        lineExtentRect = imageRect;
85        if (containingBlock->isHorizontalWritingMode()) {
86            lineExtentRect.setX(containingBlock->x());
87            lineExtentRect.setWidth(containingBlock->width());
88        } else {
89            lineExtentRect.setY(containingBlock->y());
90            lineExtentRect.setHeight(containingBlock->height());
91        }
92    } else {
93        LayoutUnit selectionTop = !containingBlock->style().isFlippedBlocksWritingMode() ? inlineBox->root().selectionTop() - logicalTop() : logicalBottom() - inlineBox->root().selectionBottom();
94        imageRect = IntRect(0,  selectionTop, logicalWidth(), inlineBox->root().selectionHeight());
95        isFirstOnLine = !inlineBox->previousOnLineExists();
96        isLastOnLine = !inlineBox->nextOnLineExists();
97        LogicalSelectionOffsetCaches cache(*containingBlock);
98        LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, inlineBox->logicalTop(), cache);
99        LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, inlineBox->logicalTop(), cache);
100        lineExtentRect = IntRect(leftOffset - logicalLeft(), imageRect.y(), rightOffset - leftOffset, imageRect.height());
101        if (!inlineBox->isHorizontal()) {
102            imageRect = imageRect.transposedRect();
103            lineExtentRect = lineExtentRect.transposedRect();
104        }
105    }
106
107    bool isFixed = false;
108    IntRect absoluteBounds = localToAbsoluteQuad(FloatRect(imageRect), false, &isFixed).enclosingBoundingBox();
109    IntRect lineExtentBounds = localToAbsoluteQuad(FloatRect(lineExtentRect)).enclosingBoundingBox();
110    if (!containingBlock->isHorizontalWritingMode())
111        lineExtentBounds = lineExtentBounds.transposedRect();
112
113    // FIXME: We should consider either making SelectionRect a struct or better organize its optional fields into
114    // an auxiliary struct to simplify its initialization.
115    rects.append(SelectionRect(absoluteBounds, containingBlock->style().direction(), lineExtentBounds.x(), lineExtentBounds.maxX(), lineExtentBounds.maxY(), 0, false /* line break */, isFirstOnLine, isLastOnLine, false /* contains start */, false /* contains end */, containingBlock->style().isHorizontalWritingMode(), isFixed, false /* ruby text */, view().pageNumberForBlockProgressionOffset(absoluteBounds.x())));
116}
117#endif
118
119using namespace HTMLNames;
120
121RenderImage::RenderImage(Element& element, PassRef<RenderStyle> style, StyleImage* styleImage, const float imageDevicePixelRatio)
122    : RenderReplaced(element, WTF::move(style), IntSize())
123    , m_imageResource(styleImage ? std::make_unique<RenderImageResourceStyleImage>(*styleImage) : std::make_unique<RenderImageResource>())
124    , m_needsToSetSizeForAltText(false)
125    , m_didIncrementVisuallyNonEmptyPixelCount(false)
126    , m_isGeneratedContent(false)
127    , m_hasShadowControls(false)
128    , m_imageDevicePixelRatio(imageDevicePixelRatio)
129{
130    updateAltText();
131    imageResource().initialize(this);
132
133    if (isHTMLImageElement(element))
134        m_hasShadowControls = toHTMLImageElement(element).hasShadowControls();
135}
136
137RenderImage::RenderImage(Document& document, PassRef<RenderStyle> style, StyleImage* styleImage)
138    : RenderReplaced(document, WTF::move(style), IntSize())
139    , m_imageResource(styleImage ? std::make_unique<RenderImageResourceStyleImage>(*styleImage) : std::make_unique<RenderImageResource>())
140    , m_needsToSetSizeForAltText(false)
141    , m_didIncrementVisuallyNonEmptyPixelCount(false)
142    , m_isGeneratedContent(false)
143    , m_hasShadowControls(false)
144    , m_imageDevicePixelRatio(1.0f)
145{
146    imageResource().initialize(this);
147}
148
149RenderImage::~RenderImage()
150{
151    imageResource().shutdown();
152}
153
154// If we'll be displaying either alt text or an image, add some padding.
155static const unsigned short paddingWidth = 4;
156static const unsigned short paddingHeight = 4;
157
158// Alt text is restricted to this maximum size, in pixels.  These are
159// signed integers because they are compared with other signed values.
160static const float maxAltTextWidth = 1024;
161static const int maxAltTextHeight = 256;
162
163IntSize RenderImage::imageSizeForError(CachedImage* newImage) const
164{
165    ASSERT_ARG(newImage, newImage);
166    ASSERT_ARG(newImage, newImage->imageForRenderer(this));
167
168    FloatSize imageSize;
169    if (newImage->willPaintBrokenImage()) {
170        std::pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(document().deviceScaleFactor());
171        imageSize = brokenImageAndImageScaleFactor.first->size();
172        imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
173    } else
174        imageSize = newImage->imageForRenderer(this)->size();
175
176    // imageSize() returns 0 for the error image. We need the true size of the
177    // error image, so we have to get it by grabbing image() directly.
178    return IntSize(paddingWidth + imageSize.width() * style().effectiveZoom(), paddingHeight + imageSize.height() * style().effectiveZoom());
179}
180
181// Sets the image height and width to fit the alt text.  Returns true if the
182// image size changed.
183bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
184{
185    IntSize imageSize;
186    if (newImage && newImage->imageForRenderer(this))
187        imageSize = imageSizeForError(newImage);
188    else if (!m_altText.isEmpty() || newImage) {
189        // If we'll be displaying either text or an image, add a little padding.
190        imageSize = IntSize(paddingWidth, paddingHeight);
191    }
192
193    // we have an alt and the user meant it (its not a text we invented)
194    if (!m_altText.isEmpty()) {
195        FontCachePurgePreventer fontCachePurgePreventer;
196
197        const Font& font = style().font();
198        IntSize paddedTextSize(paddingWidth + std::min(ceilf(font.width(RenderBlock::constructTextRun(this, font, m_altText, style()))), maxAltTextWidth), paddingHeight + std::min(font.fontMetrics().height(), maxAltTextHeight));
199        imageSize = imageSize.expandedTo(paddedTextSize);
200    }
201
202    if (imageSize == intrinsicSize())
203        return false;
204
205    setIntrinsicSize(imageSize);
206    return true;
207}
208
209void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
210{
211    RenderReplaced::styleDidChange(diff, oldStyle);
212    if (m_needsToSetSizeForAltText) {
213        if (!m_altText.isEmpty() && setImageSizeForAltText(imageResource().cachedImage()))
214            imageDimensionsChanged(true /* imageSizeChanged */);
215        m_needsToSetSizeForAltText = false;
216    }
217#if ENABLE(CSS_IMAGE_ORIENTATION)
218    if (diff == StyleDifferenceLayout && oldStyle->imageOrientation() != style().imageOrientation())
219        return imageDimensionsChanged(true /* imageSizeChanged */);
220#endif
221
222#if ENABLE(CSS_IMAGE_RESOLUTION)
223    if (diff == StyleDifferenceLayout
224        && (oldStyle->imageResolution() != style().imageResolution()
225            || oldStyle->imageResolutionSnap() != style().imageResolutionSnap()
226            || oldStyle->imageResolutionSource() != style().imageResolutionSource()))
227        imageDimensionsChanged(true /* imageSizeChanged */);
228#endif
229}
230
231void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
232{
233    // FIXME (86669): Instead of the RenderImage determining whether its document is in the page
234    // cache, the RenderImage should remove itself as a client when its document is put into the
235    // page cache.
236    if (documentBeingDestroyed() || document().inPageCache())
237        return;
238
239    if (hasBoxDecorations() || hasMask() || hasShapeOutside())
240        RenderReplaced::imageChanged(newImage, rect);
241
242    if (newImage != imageResource().imagePtr() || !newImage)
243        return;
244
245    if (!m_didIncrementVisuallyNonEmptyPixelCount) {
246        // At a zoom level of 1 the image is guaranteed to have an integer size.
247        view().frameView().incrementVisuallyNonEmptyPixelCount(flooredIntSize(imageResource().imageSize(1.0f)));
248        m_didIncrementVisuallyNonEmptyPixelCount = true;
249    }
250
251    bool imageSizeChanged = false;
252
253    // Set image dimensions, taking into account the size of the alt text.
254    if (imageResource().errorOccurred()) {
255        if (!m_altText.isEmpty() && document().hasPendingStyleRecalc()) {
256            ASSERT(element());
257            if (element()) {
258                m_needsToSetSizeForAltText = true;
259                element()->setNeedsStyleRecalc(SyntheticStyleChange);
260            }
261            return;
262        }
263        imageSizeChanged = setImageSizeForAltText(imageResource().cachedImage());
264    }
265
266    imageDimensionsChanged(imageSizeChanged, rect);
267}
268
269bool RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize, bool imageSizeChanged)
270{
271    if (newSize == intrinsicSize() && !imageSizeChanged)
272        return false;
273    if (imageResource().errorOccurred())
274        return imageSizeChanged;
275    setIntrinsicSize(newSize);
276    return true;
277}
278
279void RenderImage::updateInnerContentRect()
280{
281    // Propagate container size to image resource.
282    LayoutRect paintRect = replacedContentRect(intrinsicSize());
283    IntSize containerSize(paintRect.width(), paintRect.height());
284    if (!containerSize.isEmpty())
285        imageResource().setContainerSizeForRenderer(containerSize);
286}
287
288void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
289{
290#if ENABLE(CSS_IMAGE_RESOLUTION)
291    double scale = style().imageResolution();
292    if (style().imageResolutionSnap() == ImageResolutionSnapPixels)
293        scale = roundForImpreciseConversion<int>(scale);
294    if (scale <= 0)
295        scale = 1;
296    bool intrinsicSizeChanged = updateIntrinsicSizeIfNeeded(imageResource().intrinsicSize(style().effectiveZoom() / scale), imageSizeChanged);
297#else
298    bool intrinsicSizeChanged = updateIntrinsicSizeIfNeeded(imageResource().intrinsicSize(style().effectiveZoom()), imageSizeChanged);
299#endif
300
301    // In the case of generated image content using :before/:after/content, we might not be
302    // in the render tree yet. In that case, we just need to update our intrinsic size.
303    // layout() will be called after we are inserted in the tree which will take care of
304    // what we are doing here.
305    if (!containingBlock())
306        return;
307
308    bool shouldRepaint = true;
309    if (intrinsicSizeChanged) {
310        if (!preferredLogicalWidthsDirty())
311            setPreferredLogicalWidthsDirty(true);
312
313        bool hasOverrideSize = hasOverrideHeight() || hasOverrideWidth();
314        if (!hasOverrideSize && !imageSizeChanged) {
315            LogicalExtentComputedValues computedValues;
316            computeLogicalWidthInRegion(computedValues);
317            LayoutUnit newWidth = computedValues.m_extent;
318            computeLogicalHeight(height(), 0, computedValues);
319            LayoutUnit newHeight = computedValues.m_extent;
320
321            imageSizeChanged = width() != newWidth || height() != newHeight;
322        }
323
324        // FIXME: We only need to recompute the containing block's preferred size
325        // if the containing block's size depends on the image's size (i.e., the container uses shrink-to-fit sizing).
326        // There's no easy way to detect that shrink-to-fit is needed, always force a layout.
327        bool containingBlockNeedsToRecomputePreferredSize =
328            style().logicalWidth().isPercent()
329            || style().logicalMaxWidth().isPercent()
330            || style().logicalMinWidth().isPercent();
331
332        bool layoutSizeDependsOnIntrinsicSize = style().aspectRatioType() == AspectRatioFromIntrinsic;
333
334        if (imageSizeChanged || hasOverrideSize || containingBlockNeedsToRecomputePreferredSize || layoutSizeDependsOnIntrinsicSize) {
335            shouldRepaint = false;
336            if (!selfNeedsLayout())
337                setNeedsLayout();
338        }
339
340        if (everHadLayout() && !selfNeedsLayout()) {
341            // The inner content rectangle is calculated during layout, but may need an update now
342            // (unless the box has already been scheduled for layout). In order to calculate it, we
343            // may need values from the containing block, though, so make sure that we're not too
344            // early. It may be that layout hasn't even taken place once yet.
345
346            // FIXME: we should not have to trigger another call to setContainerSizeForRenderer()
347            // from here, since it's already being done during layout.
348            updateInnerContentRect();
349        }
350    }
351
352    if (shouldRepaint) {
353        LayoutRect repaintRect;
354        if (rect) {
355            // The image changed rect is in source image coordinates (pre-zooming),
356            // so map from the bounds of the image to the contentsBox.
357            repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), imageResource().imageSize(1.0f)), contentBoxRect()));
358            // Guard against too-large changed rects.
359            repaintRect.intersect(contentBoxRect());
360        } else
361            repaintRect = contentBoxRect();
362
363        repaintRectangle(repaintRect);
364
365        // Tell any potential compositing layers that the image needs updating.
366        contentChanged(ImageChanged);
367    }
368}
369
370void RenderImage::notifyFinished(CachedResource* newImage)
371{
372    if (documentBeingDestroyed())
373        return;
374
375    invalidateBackgroundObscurationStatus();
376
377    if (newImage == imageResource().cachedImage()) {
378        // tell any potential compositing layers
379        // that the image is done and they can reference it directly.
380        contentChanged(ImageChanged);
381    }
382}
383
384void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
385{
386    LayoutUnit cWidth = contentWidth();
387    LayoutUnit cHeight = contentHeight();
388    LayoutUnit leftBorder = borderLeft();
389    LayoutUnit topBorder = borderTop();
390    LayoutUnit leftPad = paddingLeft();
391    LayoutUnit topPad = paddingTop();
392
393    GraphicsContext* context = paintInfo.context;
394    float deviceScaleFactor = document().deviceScaleFactor();
395
396    Page* page = frame().page();
397
398    if (!imageResource().hasImage() || imageResource().errorOccurred()) {
399        if (paintInfo.phase == PaintPhaseSelection)
400            return;
401
402        if (page && paintInfo.phase == PaintPhaseForeground)
403            page->addRelevantUnpaintedObject(this, visualOverflowRect());
404
405        if (cWidth > 2 && cHeight > 2) {
406            LayoutUnit borderWidth = LayoutUnit(1 / deviceScaleFactor);
407
408            // Draw an outline rect where the image should be.
409            context->setStrokeStyle(SolidStroke);
410            context->setStrokeColor(Color::lightGray, style().colorSpace());
411            context->setFillColor(Color::transparent, style().colorSpace());
412            context->drawRect(pixelSnappedForPainting(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight), deviceScaleFactor), borderWidth);
413
414            bool errorPictureDrawn = false;
415            LayoutSize imageOffset;
416            // When calculating the usable dimensions, exclude the pixels of
417            // the ouline rect so the error image/alt text doesn't draw on it.
418            LayoutUnit usableWidth = cWidth - 2 * borderWidth;
419            LayoutUnit usableHeight = cHeight - 2 * borderWidth;
420
421            RefPtr<Image> image = imageResource().image();
422
423            if (imageResource().errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
424                // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution.
425                std::pair<Image*, float> brokenImageAndImageScaleFactor = imageResource().cachedImage()->brokenImage(document().deviceScaleFactor());
426                image = brokenImageAndImageScaleFactor.first;
427                FloatSize imageSize = image->size();
428                imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
429                // Center the error image, accounting for border and padding.
430                LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
431                if (centerX < 0)
432                    centerX = 0;
433                LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
434                if (centerY < 0)
435                    centerY = 0;
436                imageOffset = LayoutSize(leftBorder + leftPad + centerX + borderWidth, topBorder + topPad + centerY + borderWidth);
437
438                ImageOrientationDescription orientationDescription(shouldRespectImageOrientation());
439#if ENABLE(CSS_IMAGE_ORIENTATION)
440                orientationDescription.setImageOrientationEnum(style().imageOrientation());
441#endif
442                context->drawImage(image.get(), style().colorSpace(), pixelSnappedForPainting(LayoutRect(paintOffset + imageOffset, imageSize), deviceScaleFactor), orientationDescription);
443                errorPictureDrawn = true;
444            }
445
446            if (!m_altText.isEmpty()) {
447                String text = document().displayStringModifiedByEncoding(m_altText);
448                context->setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace());
449                const Font& font = style().font();
450                const FontMetrics& fontMetrics = font.fontMetrics();
451                LayoutUnit ascent = fontMetrics.ascent();
452                LayoutPoint altTextOffset = paintOffset;
453                altTextOffset.move(leftBorder + leftPad + (paddingWidth / 2) - borderWidth, topBorder + topPad + ascent + (paddingHeight / 2) - borderWidth);
454
455                // Only draw the alt text if it'll fit within the content box,
456                // and only if it fits above the error image.
457                TextRun textRun = RenderBlock::constructTextRun(this, font, text, style());
458                LayoutUnit textWidth = font.width(textRun);
459                if (errorPictureDrawn) {
460                    if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
461                        context->drawText(font, textRun, altTextOffset);
462                } else if (usableWidth >= textWidth && usableHeight >= fontMetrics.height())
463                    context->drawText(font, textRun, altTextOffset);
464            }
465        }
466    } else if (imageResource().hasImage() && cWidth > 0 && cHeight > 0) {
467        RefPtr<Image> img = imageResource().image(cWidth, cHeight);
468        if (!img || img->isNull()) {
469            if (page && paintInfo.phase == PaintPhaseForeground)
470                page->addRelevantUnpaintedObject(this, visualOverflowRect());
471            return;
472        }
473
474        LayoutRect contentRect = contentBoxRect();
475        contentRect.moveBy(paintOffset);
476        LayoutRect paintRect = replacedContentRect(intrinsicSize());
477        paintRect.moveBy(paintOffset);
478        bool clip = !contentRect.contains(paintRect);
479        GraphicsContextStateSaver stateSaver(*context, clip);
480        if (clip)
481            context->clip(contentRect);
482
483        paintIntoRect(context, pixelSnappedForPainting(paintRect, deviceScaleFactor));
484
485        if (cachedImage() && page && paintInfo.phase == PaintPhaseForeground) {
486            // For now, count images as unpainted if they are still progressively loading. We may want
487            // to refine this in the future to account for the portion of the image that has painted.
488            LayoutRect visibleRect = intersection(paintRect, contentRect);
489            if (cachedImage()->isLoading())
490                page->addRelevantUnpaintedObject(this, visibleRect);
491            else
492                page->addRelevantRepaintedObject(this, visibleRect);
493        }
494    }
495}
496
497void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
498{
499    RenderReplaced::paint(paintInfo, paintOffset);
500
501    if (paintInfo.phase == PaintPhaseOutline)
502        paintAreaElementFocusRing(paintInfo);
503}
504
505void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
506{
507#if PLATFORM(IOS)
508    UNUSED_PARAM(paintInfo);
509#else
510    if (document().printing() || !frame().selection().isFocusedAndActive())
511        return;
512
513    if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
514        return;
515
516    Element* focusedElement = document().focusedElement();
517    if (!focusedElement || !isHTMLAreaElement(focusedElement))
518        return;
519
520    HTMLAreaElement* areaElement = toHTMLAreaElement(focusedElement);
521    if (areaElement->imageElement() != element())
522        return;
523
524    // Even if the theme handles focus ring drawing for entire elements, it won't do it for
525    // an area within an image, so we don't call RenderTheme::supportsFocusRing here.
526
527    Path path = areaElement->computePath(this);
528    if (path.isEmpty())
529        return;
530
531    // FIXME: Do we need additional code to clip the path to the image's bounding box?
532
533    RenderStyle* areaElementStyle = areaElement->computedStyle();
534    unsigned short outlineWidth = areaElementStyle->outlineWidth();
535    if (!outlineWidth)
536        return;
537
538    paintInfo.context->drawFocusRing(path, outlineWidth,
539        areaElementStyle->outlineOffset(),
540        areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor));
541#endif
542}
543
544void RenderImage::areaElementFocusChanged(HTMLAreaElement* element)
545{
546    ASSERT_UNUSED(element, element->imageElement() == this->element());
547
548    // It would be more efficient to only repaint the focus ring rectangle
549    // for the passed-in area element. That would require adding functions
550    // to the area element class.
551    repaint();
552}
553
554void RenderImage::paintIntoRect(GraphicsContext* context, const FloatRect& rect)
555{
556    if (!imageResource().hasImage() || imageResource().errorOccurred() || rect.width() <= 0 || rect.height() <= 0)
557        return;
558
559    RefPtr<Image> img = imageResource().image(rect.width(), rect.height());
560    if (!img || img->isNull())
561        return;
562
563    HTMLImageElement* imageElt = (element() && isHTMLImageElement(element())) ? toHTMLImageElement(element()) : 0;
564    CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
565    Image* image = imageResource().image().get();
566    bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, LayoutSize(rect.size()));
567    ImageOrientationDescription orientationDescription(shouldRespectImageOrientation());
568#if ENABLE(CSS_IMAGE_ORIENTATION)
569    orientationDescription.setImageOrientationEnum(style().imageOrientation());
570#endif
571    context->drawImage(imageResource().image(rect.width(), rect.height()).get(), style().colorSpace(), rect,
572        ImagePaintingOptions(compositeOperator, BlendModeNormal, orientationDescription, useLowQualityScaling));
573}
574
575bool RenderImage::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox*) const
576{
577    if (!RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(bleedAvoidance))
578        return false;
579
580    return !const_cast<RenderImage*>(this)->backgroundIsKnownToBeObscured();
581}
582
583bool RenderImage::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const
584{
585    UNUSED_PARAM(maxDepthToTest);
586    if (!imageResource().hasImage() || imageResource().errorOccurred())
587        return false;
588    if (imageResource().cachedImage() && !imageResource().cachedImage()->isLoaded())
589        return false;
590    if (!contentBoxRect().contains(localRect))
591        return false;
592    EFillBox backgroundClip = style().backgroundClip();
593    // Background paints under borders.
594    if (backgroundClip == BorderFillBox && style().hasBorder() && !borderObscuresBackground())
595        return false;
596    // Background shows in padding area.
597    if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style().hasPadding())
598        return false;
599    // Object-fit may leave parts of the content box empty.
600    ObjectFit objectFit = style().objectFit();
601    if (objectFit != ObjectFitFill && objectFit != ObjectFitCover)
602        return false;
603    // Check for image with alpha.
604    return imageResource().cachedImage() && imageResource().cachedImage()->currentFrameKnownToBeOpaque(this);
605}
606
607bool RenderImage::computeBackgroundIsKnownToBeObscured()
608{
609    if (!hasBackground())
610        return false;
611
612    LayoutRect paintedExtent;
613    if (!getBackgroundPaintedExtent(paintedExtent))
614        return false;
615    return foregroundIsKnownToBeOpaqueInRect(paintedExtent, 0);
616}
617
618LayoutUnit RenderImage::minimumReplacedHeight() const
619{
620    return imageResource().errorOccurred() ? intrinsicSize().height() : LayoutUnit();
621}
622
623HTMLMapElement* RenderImage::imageMap() const
624{
625    HTMLImageElement* i = element() && isHTMLImageElement(element()) ? toHTMLImageElement(element()) : 0;
626    return i ? i->treeScope().getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
627}
628
629bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
630{
631    HitTestResult tempResult(result.hitTestLocation());
632    bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction);
633
634    if (tempResult.innerNode() && element()) {
635        if (HTMLMapElement* map = imageMap()) {
636            LayoutRect contentBox = contentBoxRect();
637            float scaleFactor = 1 / style().effectiveZoom();
638            LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location());
639            mapLocation.scale(scaleFactor, scaleFactor);
640
641            if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
642                tempResult.setInnerNonSharedNode(element());
643        }
644    }
645
646    if (!inside && result.isRectBasedTest())
647        result.append(tempResult);
648    if (inside)
649        result = tempResult;
650    return inside;
651}
652
653void RenderImage::updateAltText()
654{
655    if (!element())
656        return;
657
658    if (isHTMLInputElement(element()))
659        m_altText = toHTMLInputElement(element())->altText();
660    else if (isHTMLImageElement(element()))
661        m_altText = toHTMLImageElement(element())->altText();
662}
663
664bool RenderImage::canHaveChildren() const
665{
666#if !ENABLE(SERVICE_CONTROLS)
667    return false;
668#else
669    return m_hasShadowControls;
670#endif
671}
672
673void RenderImage::layout()
674{
675    StackStats::LayoutCheckPoint layoutCheckPoint;
676
677    LayoutSize oldSize = contentBoxRect().size();
678    RenderReplaced::layout();
679
680    updateInnerContentRect();
681
682    if (m_hasShadowControls)
683        layoutShadowControls(oldSize);
684}
685
686void RenderImage::layoutShadowControls(const LayoutSize& oldSize)
687{
688    RenderBox* controlsRenderer = toRenderBox(firstChild());
689    if (!controlsRenderer)
690        return;
691
692    bool controlsNeedLayout = controlsRenderer->needsLayout();
693    // If the region chain has changed we also need to relayout the controls to update the region box info.
694    // FIXME: We can do better once we compute region box info for RenderReplaced, not only for RenderBlock.
695    const RenderFlowThread* flowThread = flowThreadContainingBlock();
696    if (flowThread && !controlsNeedLayout) {
697        if (flowThread->pageLogicalSizeChanged())
698            controlsNeedLayout = true;
699    }
700
701    LayoutSize newSize = contentBoxRect().size();
702    if (newSize == oldSize && !controlsNeedLayout)
703        return;
704
705    // When calling layout() on a child node, a parent must either push a LayoutStateMaintainter, or
706    // instantiate LayoutStateDisabler. Since using a LayoutStateMaintainer is slightly more efficient,
707    // and this method might be called many times per second during video playback, use a LayoutStateMaintainer:
708    LayoutStateMaintainer statePusher(view(), *this, locationOffset(), hasTransform() || hasReflection() || style().isFlippedBlocksWritingMode());
709
710    if (shadowControlsNeedCustomLayoutMetrics()) {
711        controlsRenderer->setLocation(LayoutPoint(borderLeft(), borderTop()) + LayoutSize(paddingLeft(), paddingTop()));
712        controlsRenderer->style().setHeight(Length(newSize.height(), Fixed));
713        controlsRenderer->style().setWidth(Length(newSize.width(), Fixed));
714    }
715
716    controlsRenderer->setNeedsLayout(MarkOnlyThis);
717    controlsRenderer->layout();
718    clearChildNeedsLayout();
719
720    statePusher.pop();
721}
722
723void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const
724{
725    RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio);
726
727    // Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use.
728    if (intrinsicSize.isEmpty() && (imageResource().imageHasRelativeWidth() || imageResource().imageHasRelativeHeight())) {
729        RenderObject* containingBlock = isOutOfFlowPositioned() ? container() : this->containingBlock();
730        if (containingBlock->isBox()) {
731            RenderBox* box = toRenderBox(containingBlock);
732            intrinsicSize.setWidth(box->availableLogicalWidth());
733            intrinsicSize.setHeight(box->availableLogicalHeight(IncludeMarginBorderPadding));
734        }
735    }
736    // Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image.
737    if (imageResource().errorOccurred()) {
738        intrinsicRatio = 1;
739        return;
740    }
741}
742
743bool RenderImage::needsPreferredWidthsRecalculation() const
744{
745    if (RenderReplaced::needsPreferredWidthsRecalculation())
746        return true;
747    return embeddedContentBox();
748}
749
750RenderBox* RenderImage::embeddedContentBox() const
751{
752    CachedImage* cachedImage = imageResource().cachedImage();
753    if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
754        return static_cast<SVGImage*>(cachedImage->image())->embeddedContentBox();
755
756    return 0;
757}
758
759} // namespace WebCore
760