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