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