1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * Copyright (C) 2000 Dirk Mueller (mueller (at) kde.org) 4 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 5 * Copyright (C) Research In Motion Limited 2011-2012. All rights reserved. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 * 22 */ 23 24 #include "config.h" 25 #include "core/rendering/RenderReplaced.h" 26 27 #include "core/rendering/GraphicsContextAnnotator.h" 28 #include "core/rendering/LayoutRepainter.h" 29 #include "core/rendering/RenderBlock.h" 30 #include "core/rendering/RenderImage.h" 31 #include "core/rendering/RenderLayer.h" 32 #include "core/rendering/RenderView.h" 33 #include "platform/LengthFunctions.h" 34 #include "platform/RuntimeEnabledFeatures.h" 35 #include "platform/graphics/GraphicsContext.h" 36 37 using namespace std; 38 39 namespace WebCore { 40 41 const int cDefaultWidth = 300; 42 const int cDefaultHeight = 150; 43 44 RenderReplaced::RenderReplaced(Element* element) 45 : RenderBox(element) 46 , m_intrinsicSize(cDefaultWidth, cDefaultHeight) 47 { 48 setReplaced(true); 49 } 50 51 RenderReplaced::RenderReplaced(Element* element, const LayoutSize& intrinsicSize) 52 : RenderBox(element) 53 , m_intrinsicSize(intrinsicSize) 54 { 55 setReplaced(true); 56 } 57 58 RenderReplaced::~RenderReplaced() 59 { 60 } 61 62 void RenderReplaced::willBeDestroyed() 63 { 64 if (!documentBeingDestroyed() && parent()) 65 parent()->dirtyLinesFromChangedChild(this); 66 67 RenderBox::willBeDestroyed(); 68 } 69 70 void RenderReplaced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 71 { 72 RenderBox::styleDidChange(diff, oldStyle); 73 74 bool hadStyle = (oldStyle != 0); 75 float oldZoom = hadStyle ? oldStyle->effectiveZoom() : RenderStyle::initialZoom(); 76 if (style() && style()->effectiveZoom() != oldZoom) 77 intrinsicSizeChanged(); 78 } 79 80 void RenderReplaced::layout() 81 { 82 ASSERT(needsLayout()); 83 84 LayoutRepainter repainter(*this, checkForPaintInvalidationDuringLayout()); 85 86 setHeight(minimumReplacedHeight()); 87 88 updateLogicalWidth(); 89 updateLogicalHeight(); 90 91 m_overflow.clear(); 92 addVisualEffectOverflow(); 93 updateLayerTransformAfterLayout(); 94 invalidateBackgroundObscurationStatus(); 95 96 repainter.repaintAfterLayout(); 97 clearNeedsLayout(); 98 } 99 100 void RenderReplaced::intrinsicSizeChanged() 101 { 102 int scaledWidth = static_cast<int>(cDefaultWidth * style()->effectiveZoom()); 103 int scaledHeight = static_cast<int>(cDefaultHeight * style()->effectiveZoom()); 104 m_intrinsicSize = IntSize(scaledWidth, scaledHeight); 105 setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(); 106 } 107 108 void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 109 { 110 ANNOTATE_GRAPHICS_CONTEXT(paintInfo, this); 111 112 if (!shouldPaint(paintInfo, paintOffset)) 113 return; 114 115 LayoutPoint adjustedPaintOffset = paintOffset + location(); 116 117 if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) 118 paintBoxDecorations(paintInfo, adjustedPaintOffset); 119 120 if (paintInfo.phase == PaintPhaseMask) { 121 paintMask(paintInfo, adjustedPaintOffset); 122 return; 123 } 124 125 if (paintInfo.phase == PaintPhaseClippingMask && (!hasLayer() || !layer()->hasCompositedClippingMask())) 126 return; 127 128 LayoutRect paintRect = LayoutRect(adjustedPaintOffset, size()); 129 if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) 130 paintOutline(paintInfo, paintRect); 131 132 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && !canHaveChildren() && paintInfo.phase != PaintPhaseClippingMask) 133 return; 134 135 if (!paintInfo.shouldPaintWithinRoot(this)) 136 return; 137 138 bool drawSelectionTint = selectionState() != SelectionNone && !document().printing(); 139 if (paintInfo.phase == PaintPhaseSelection) { 140 if (selectionState() == SelectionNone) 141 return; 142 drawSelectionTint = false; 143 } 144 145 bool completelyClippedOut = false; 146 if (style()->hasBorderRadius()) { 147 LayoutRect borderRect = LayoutRect(adjustedPaintOffset, size()); 148 149 if (borderRect.isEmpty()) 150 completelyClippedOut = true; 151 else { 152 // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. 153 paintInfo.context->save(); 154 RoundedRect roundedInnerRect = style()->getRoundedInnerBorderFor(paintRect, 155 paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), true, true); 156 clipRoundedInnerRect(paintInfo.context, paintRect, roundedInnerRect); 157 } 158 } 159 160 if (!completelyClippedOut) { 161 if (paintInfo.phase == PaintPhaseClippingMask) { 162 paintClippingMask(paintInfo, adjustedPaintOffset); 163 } else { 164 paintReplaced(paintInfo, adjustedPaintOffset); 165 } 166 167 if (style()->hasBorderRadius()) 168 paintInfo.context->restore(); 169 } 170 171 // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of 172 // surrounding content. 173 if (drawSelectionTint) { 174 LayoutRect selectionPaintingRect = localSelectionRect(); 175 selectionPaintingRect.moveBy(adjustedPaintOffset); 176 paintInfo.context->fillRect(pixelSnappedIntRect(selectionPaintingRect), selectionBackgroundColor()); 177 } 178 } 179 180 bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 181 { 182 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline 183 && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask && paintInfo.phase != PaintPhaseClippingMask) 184 return false; 185 186 if (!paintInfo.shouldPaintWithinRoot(this)) 187 return false; 188 189 // if we're invisible or haven't received a layout yet, then just bail. 190 if (style()->visibility() != VISIBLE) 191 return false; 192 193 LayoutPoint adjustedPaintOffset = paintOffset + location(); 194 195 // Early exit if the element touches the edges. 196 LayoutUnit top = adjustedPaintOffset.y() + visualOverflowRect().y(); 197 LayoutUnit bottom = adjustedPaintOffset.y() + visualOverflowRect().maxY(); 198 if (isSelected() && inlineBoxWrapper()) { 199 LayoutUnit selTop = paintOffset.y() + inlineBoxWrapper()->root().selectionTop(); 200 LayoutUnit selBottom = paintOffset.y() + selTop + inlineBoxWrapper()->root().selectionHeight(); 201 top = min(selTop, top); 202 bottom = max(selBottom, bottom); 203 } 204 205 if (adjustedPaintOffset.x() + visualOverflowRect().x() >= paintInfo.rect.maxX() || adjustedPaintOffset.x() + visualOverflowRect().maxX() <= paintInfo.rect.x()) 206 return false; 207 208 if (top >= paintInfo.rect.maxY() || bottom <= paintInfo.rect.y()) 209 return false; 210 211 return true; 212 } 213 214 static inline RenderBlock* firstContainingBlockWithLogicalWidth(const RenderReplaced* replaced) 215 { 216 // We have to lookup the containing block, which has an explicit width, which must not be equal to our direct containing block. 217 // If the embedded document appears _after_ we performed the initial layout, our intrinsic size is 300x150. If our containing 218 // block doesn't provide an explicit width, it's set to the 300 default, coming from the initial layout run. 219 RenderBlock* containingBlock = replaced->containingBlock(); 220 if (!containingBlock) 221 return 0; 222 223 for (; !containingBlock->isRenderView() && !containingBlock->isBody(); containingBlock = containingBlock->containingBlock()) { 224 if (containingBlock->style()->logicalWidth().isSpecified() 225 && containingBlock->style()->logicalMinWidth().isSpecified() 226 && (containingBlock->style()->logicalMaxWidth().isSpecified() || containingBlock->style()->logicalMaxWidth().isUndefined())) 227 return containingBlock; 228 } 229 230 return 0; 231 } 232 233 bool RenderReplaced::hasReplacedLogicalHeight() const 234 { 235 if (style()->logicalHeight().isAuto()) 236 return false; 237 238 if (style()->logicalHeight().isSpecified()) { 239 if (hasAutoHeightOrContainingBlockWithAutoHeight()) 240 return false; 241 return true; 242 } 243 244 if (style()->logicalHeight().isIntrinsic()) 245 return true; 246 247 return false; 248 } 249 250 bool RenderReplaced::needsPreferredWidthsRecalculation() const 251 { 252 // If the height is a percentage and the width is auto, then the containingBlocks's height changing can cause 253 // this node to change it's preferred width because it maintains aspect ratio. 254 return hasRelativeLogicalHeight() && style()->logicalWidth().isAuto() && !hasAutoHeightOrContainingBlockWithAutoHeight(); 255 } 256 257 static inline bool rendererHasAspectRatio(const RenderObject* renderer) 258 { 259 ASSERT(renderer); 260 return renderer->isImage() || renderer->isCanvas() || renderer->isVideo(); 261 } 262 263 void RenderReplaced::computeAspectRatioInformationForRenderBox(RenderBox* contentRenderer, FloatSize& constrainedSize, double& intrinsicRatio) const 264 { 265 FloatSize intrinsicSize; 266 if (contentRenderer) { 267 contentRenderer->computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio); 268 269 // Handle zoom & vertical writing modes here, as the embedded document doesn't know about them. 270 intrinsicSize.scale(style()->effectiveZoom()); 271 if (isRenderImage()) 272 intrinsicSize.scale(toRenderImage(this)->imageDevicePixelRatio()); 273 274 // Update our intrinsic size to match what the content renderer has computed, so that when we 275 // constrain the size below, the correct intrinsic size will be obtained for comparison against 276 // min and max widths. 277 if (intrinsicRatio && !intrinsicSize.isEmpty()) 278 m_intrinsicSize = LayoutSize(intrinsicSize); 279 280 if (!isHorizontalWritingMode()) { 281 if (intrinsicRatio) 282 intrinsicRatio = 1 / intrinsicRatio; 283 intrinsicSize = intrinsicSize.transposedSize(); 284 } 285 } else { 286 computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio); 287 if (intrinsicRatio && !intrinsicSize.isEmpty()) 288 m_intrinsicSize = LayoutSize(isHorizontalWritingMode() ? intrinsicSize : intrinsicSize.transposedSize()); 289 } 290 291 // Now constrain the intrinsic size along each axis according to minimum and maximum width/heights along the 292 // opposite axis. So for example a maximum width that shrinks our width will result in the height we compute here 293 // having to shrink in order to preserve the aspect ratio. Because we compute these values independently along 294 // each axis, the final returned size may in fact not preserve the aspect ratio. 295 // FIXME: In the long term, it might be better to just return this code more to the way it used to be before this 296 // function was added, since all it has done is make the code more unclear. 297 constrainedSize = intrinsicSize; 298 if (intrinsicRatio && !intrinsicSize.isEmpty() && style()->logicalWidth().isAuto() && style()->logicalHeight().isAuto()) { 299 // We can't multiply or divide by 'intrinsicRatio' here, it breaks tests, like fast/images/zoomed-img-size.html, which 300 // can only be fixed once subpixel precision is available for things like intrinsicWidth/Height - which include zoom! 301 constrainedSize.setWidth(RenderBox::computeReplacedLogicalHeight() * intrinsicSize.width() / intrinsicSize.height()); 302 constrainedSize.setHeight(RenderBox::computeReplacedLogicalWidth() * intrinsicSize.height() / intrinsicSize.width()); 303 } 304 } 305 306 LayoutRect RenderReplaced::replacedContentRect(const LayoutSize* overriddenIntrinsicSize) const 307 { 308 LayoutRect contentRect = contentBoxRect(); 309 ObjectFit objectFit = style()->objectFit(); 310 311 if (objectFit == ObjectFitFill && style()->objectPosition() == RenderStyle::initialObjectPosition()) { 312 if (!isVideo() || RuntimeEnabledFeatures::objectFitPositionEnabled()) 313 return contentRect; 314 objectFit = ObjectFitContain; 315 } 316 317 LayoutSize intrinsicSize = overriddenIntrinsicSize ? *overriddenIntrinsicSize : this->intrinsicSize(); 318 if (!intrinsicSize.width() || !intrinsicSize.height()) 319 return contentRect; 320 321 LayoutRect finalRect = contentRect; 322 switch (objectFit) { 323 case ObjectFitContain: 324 case ObjectFitScaleDown: 325 case ObjectFitCover: 326 finalRect.setSize(finalRect.size().fitToAspectRatio(intrinsicSize, objectFit == ObjectFitCover ? AspectRatioFitGrow : AspectRatioFitShrink)); 327 if (objectFit != ObjectFitScaleDown || finalRect.width() <= intrinsicSize.width()) 328 break; 329 // fall through 330 case ObjectFitNone: 331 finalRect.setSize(intrinsicSize); 332 break; 333 case ObjectFitFill: 334 break; 335 default: 336 ASSERT_NOT_REACHED(); 337 } 338 339 LayoutUnit xOffset = minimumValueForLength(style()->objectPosition().x(), contentRect.width() - finalRect.width()); 340 LayoutUnit yOffset = minimumValueForLength(style()->objectPosition().y(), contentRect.height() - finalRect.height()); 341 finalRect.move(xOffset, yOffset); 342 343 return finalRect; 344 } 345 346 void RenderReplaced::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const 347 { 348 // If there's an embeddedContentBox() of a remote, referenced document available, this code-path should never be used. 349 ASSERT(!embeddedContentBox()); 350 intrinsicSize = FloatSize(intrinsicLogicalWidth().toFloat(), intrinsicLogicalHeight().toFloat()); 351 352 // Figure out if we need to compute an intrinsic ratio. 353 if (intrinsicSize.isEmpty() || !rendererHasAspectRatio(this)) 354 return; 355 356 intrinsicRatio = intrinsicSize.width() / intrinsicSize.height(); 357 } 358 359 LayoutUnit RenderReplaced::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const 360 { 361 if (style()->logicalWidth().isSpecified() || style()->logicalWidth().isIntrinsic()) 362 return computeReplacedLogicalWidthRespectingMinMaxWidth(computeReplacedLogicalWidthUsing(style()->logicalWidth()), shouldComputePreferred); 363 364 RenderBox* contentRenderer = embeddedContentBox(); 365 366 // 10.3.2 Inline, replaced elements: http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width 367 double intrinsicRatio = 0; 368 FloatSize constrainedSize; 369 computeAspectRatioInformationForRenderBox(contentRenderer, constrainedSize, intrinsicRatio); 370 371 if (style()->logicalWidth().isAuto()) { 372 bool computedHeightIsAuto = hasAutoHeightOrContainingBlockWithAutoHeight(); 373 bool hasIntrinsicWidth = constrainedSize.width() > 0; 374 375 // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, then that intrinsic width is the used value of 'width'. 376 if (computedHeightIsAuto && hasIntrinsicWidth) 377 return computeReplacedLogicalWidthRespectingMinMaxWidth(constrainedSize.width(), shouldComputePreferred); 378 379 bool hasIntrinsicHeight = constrainedSize.height() > 0; 380 if (intrinsicRatio) { 381 // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, but does have an intrinsic height and intrinsic ratio; 382 // or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value 383 // of 'width' is: (used height) * (intrinsic ratio) 384 if (intrinsicRatio && ((computedHeightIsAuto && !hasIntrinsicWidth && hasIntrinsicHeight) || !computedHeightIsAuto)) { 385 LayoutUnit logicalHeight = computeReplacedLogicalHeight(); 386 return computeReplacedLogicalWidthRespectingMinMaxWidth(roundToInt(round(logicalHeight * intrinsicRatio)), shouldComputePreferred); 387 } 388 389 // If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width, then the used value of 390 // 'width' is undefined in CSS 2.1. However, it is suggested that, if the containing block's width does not itself depend on the replaced element's width, then 391 // the used value of 'width' is calculated from the constraint equation used for block-level, non-replaced elements in normal flow. 392 if (computedHeightIsAuto && !hasIntrinsicWidth && !hasIntrinsicHeight) { 393 if (shouldComputePreferred == ComputePreferred) 394 return 0; 395 // The aforementioned 'constraint equation' used for block-level, non-replaced elements in normal flow: 396 // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block 397 LayoutUnit logicalWidth; 398 // FIXME: This walking up the containgBlock chain to find the first one with a specified width is bonkers. 399 // If nothing else, it requires making sure that computeReplacedLogicalWidthRespectingMinMaxWidth cannot 400 // depend on the width of the replaced element or we infinite loop. Right now we do that in 401 // firstContainingBlockWithLogicalWidth by checking that width/min-width/max-width are all specified. 402 // 403 // Firefox 27 seems to only do this if the <svg> has a viewbox. 404 if (RenderBlock* blockWithWidth = firstContainingBlockWithLogicalWidth(this)) { 405 logicalWidth = blockWithWidth->computeReplacedLogicalWidthRespectingMinMaxWidth(blockWithWidth->computeReplacedLogicalWidthUsing(blockWithWidth->style()->logicalWidth()), shouldComputePreferred); 406 } else { 407 // FIXME: If shouldComputePreferred == ComputePreferred, then we're reading this during preferred width 408 // computation, at which point this is reading stale data from a previous layout. 409 logicalWidth = containingBlock()->availableLogicalWidth(); 410 } 411 412 // This solves above equation for 'width' (== logicalWidth). 413 LayoutUnit marginStart = minimumValueForLength(style()->marginStart(), logicalWidth); 414 LayoutUnit marginEnd = minimumValueForLength(style()->marginEnd(), logicalWidth); 415 logicalWidth = max<LayoutUnit>(0, logicalWidth - (marginStart + marginEnd + (width() - clientWidth()))); 416 return computeReplacedLogicalWidthRespectingMinMaxWidth(logicalWidth, shouldComputePreferred); 417 } 418 } 419 420 // Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'. 421 if (hasIntrinsicWidth) 422 return computeReplacedLogicalWidthRespectingMinMaxWidth(constrainedSize.width(), shouldComputePreferred); 423 424 // Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px. If 300px is too 425 // wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead. 426 // Note: We fall through and instead return intrinsicLogicalWidth() here - to preserve existing WebKit behavior, which might or might not be correct, or desired. 427 // Changing this to return cDefaultWidth, will affect lots of test results. Eg. some tests assume that a blank <img> tag (which implies width/height=auto) 428 // has no intrinsic size, which is wrong per CSS 2.1, but matches our behavior since a long time. 429 } 430 431 return computeReplacedLogicalWidthRespectingMinMaxWidth(intrinsicLogicalWidth(), shouldComputePreferred); 432 } 433 434 LayoutUnit RenderReplaced::computeReplacedLogicalHeight() const 435 { 436 // 10.5 Content height: the 'height' property: http://www.w3.org/TR/CSS21/visudet.html#propdef-height 437 if (hasReplacedLogicalHeight()) 438 return computeReplacedLogicalHeightRespectingMinMaxHeight(computeReplacedLogicalHeightUsing(style()->logicalHeight())); 439 440 RenderBox* contentRenderer = embeddedContentBox(); 441 442 // 10.6.2 Inline, replaced elements: http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height 443 double intrinsicRatio = 0; 444 FloatSize constrainedSize; 445 computeAspectRatioInformationForRenderBox(contentRenderer, constrainedSize, intrinsicRatio); 446 447 bool widthIsAuto = style()->logicalWidth().isAuto(); 448 bool hasIntrinsicHeight = constrainedSize.height() > 0; 449 450 // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic height, then that intrinsic height is the used value of 'height'. 451 if (widthIsAuto && hasIntrinsicHeight) 452 return computeReplacedLogicalHeightRespectingMinMaxHeight(constrainedSize.height()); 453 454 // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is: 455 // (used width) / (intrinsic ratio) 456 if (intrinsicRatio) 457 return computeReplacedLogicalHeightRespectingMinMaxHeight(roundToInt(round(availableLogicalWidth() / intrinsicRatio))); 458 459 // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'. 460 if (hasIntrinsicHeight) 461 return computeReplacedLogicalHeightRespectingMinMaxHeight(constrainedSize.height()); 462 463 // Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'height' must be set to the height 464 // of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px, and has a width not greater than the device width. 465 return computeReplacedLogicalHeightRespectingMinMaxHeight(intrinsicLogicalHeight()); 466 } 467 468 void RenderReplaced::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const 469 { 470 minLogicalWidth = maxLogicalWidth = intrinsicLogicalWidth(); 471 } 472 473 void RenderReplaced::computePreferredLogicalWidths() 474 { 475 ASSERT(preferredLogicalWidthsDirty()); 476 477 // We cannot resolve any percent logical width here as the available logical 478 // width may not be set on our containing block. 479 if (style()->logicalWidth().isPercent()) 480 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); 481 else 482 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeReplacedLogicalWidth(ComputePreferred); 483 484 RenderStyle* styleToUse = style(); 485 if (styleToUse->logicalWidth().isPercent() || styleToUse->logicalMaxWidth().isPercent()) 486 m_minPreferredLogicalWidth = 0; 487 488 if (styleToUse->logicalMinWidth().isFixed() && styleToUse->logicalMinWidth().value() > 0) { 489 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMinWidth().value())); 490 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMinWidth().value())); 491 } 492 493 if (styleToUse->logicalMaxWidth().isFixed()) { 494 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMaxWidth().value())); 495 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMaxWidth().value())); 496 } 497 498 LayoutUnit borderAndPadding = borderAndPaddingLogicalWidth(); 499 m_minPreferredLogicalWidth += borderAndPadding; 500 m_maxPreferredLogicalWidth += borderAndPadding; 501 502 clearPreferredLogicalWidthsDirty(); 503 } 504 505 PositionWithAffinity RenderReplaced::positionForPoint(const LayoutPoint& point) 506 { 507 // FIXME: This code is buggy if the replaced element is relative positioned. 508 InlineBox* box = inlineBoxWrapper(); 509 RootInlineBox* rootBox = box ? &box->root() : 0; 510 511 LayoutUnit top = rootBox ? rootBox->selectionTop() : logicalTop(); 512 LayoutUnit bottom = rootBox ? rootBox->selectionBottom() : logicalBottom(); 513 514 LayoutUnit blockDirectionPosition = isHorizontalWritingMode() ? point.y() + y() : point.x() + x(); 515 LayoutUnit lineDirectionPosition = isHorizontalWritingMode() ? point.x() + x() : point.y() + y(); 516 517 if (blockDirectionPosition < top) 518 return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); // coordinates are above 519 520 if (blockDirectionPosition >= bottom) 521 return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM); // coordinates are below 522 523 if (node()) { 524 if (lineDirectionPosition <= logicalLeft() + (logicalWidth() / 2)) 525 return createPositionWithAffinity(0, DOWNSTREAM); 526 return createPositionWithAffinity(1, DOWNSTREAM); 527 } 528 529 return RenderBox::positionForPoint(point); 530 } 531 532 LayoutRect RenderReplaced::selectionRectForPaintInvalidation(const RenderLayerModelObject* paintInvalidationContainer, bool clipToVisibleContent) 533 { 534 ASSERT(!needsLayout()); 535 536 if (!isSelected()) 537 return LayoutRect(); 538 539 LayoutRect rect = localSelectionRect(); 540 if (clipToVisibleContent) 541 mapRectToPaintInvalidationBacking(paintInvalidationContainer, rect); 542 else 543 rect = localToContainerQuad(FloatRect(rect), paintInvalidationContainer).enclosingBoundingBox(); 544 545 return rect; 546 } 547 548 LayoutRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const 549 { 550 if (checkWhetherSelected && !isSelected()) 551 return LayoutRect(); 552 553 if (!inlineBoxWrapper()) 554 // We're a block-level replaced element. Just return our own dimensions. 555 return LayoutRect(LayoutPoint(), size()); 556 557 RootInlineBox& root = inlineBoxWrapper()->root(); 558 LayoutUnit newLogicalTop = root.block().style()->isFlippedBlocksWritingMode() ? inlineBoxWrapper()->logicalBottom() - root.selectionBottom() : root.selectionTop() - inlineBoxWrapper()->logicalTop(); 559 if (root.block().style()->isHorizontalWritingMode()) 560 return LayoutRect(0, newLogicalTop, width(), root.selectionHeight()); 561 return LayoutRect(newLogicalTop, 0, root.selectionHeight(), height()); 562 } 563 564 void RenderReplaced::setSelectionState(SelectionState state) 565 { 566 // The selection state for our containing block hierarchy is updated by the base class call. 567 RenderBox::setSelectionState(state); 568 569 if (!inlineBoxWrapper()) 570 return; 571 572 // We only include the space below the baseline in our layer's cached repaint rect if the 573 // image is selected. Since the selection state has changed update the rect. 574 if (hasLayer()) 575 layer()->repainter().computeRepaintRects(); 576 577 if (canUpdateSelectionOnRootLineBoxes()) 578 inlineBoxWrapper()->root().setHasSelectedChildren(isSelected()); 579 } 580 581 bool RenderReplaced::isSelected() const 582 { 583 SelectionState s = selectionState(); 584 if (s == SelectionNone) 585 return false; 586 if (s == SelectionInside) 587 return true; 588 589 int selectionStart, selectionEnd; 590 selectionStartEnd(selectionStart, selectionEnd); 591 if (s == SelectionStart) 592 return selectionStart == 0; 593 594 int end = node()->hasChildren() ? node()->countChildren() : 1; 595 if (s == SelectionEnd) 596 return selectionEnd == end; 597 if (s == SelectionBoth) 598 return selectionStart == 0 && selectionEnd == end; 599 600 ASSERT(0); 601 return false; 602 } 603 LayoutRect RenderReplaced::clippedOverflowRectForPaintInvalidation(const RenderLayerModelObject* paintInvalidationContainer) const 604 { 605 if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent()) 606 return LayoutRect(); 607 608 // The selectionRect can project outside of the overflowRect, so take their union 609 // for repainting to avoid selection painting glitches. 610 LayoutRect r = isSelected() ? localSelectionRect() : visualOverflowRect(); 611 612 RenderView* v = view(); 613 if (!RuntimeEnabledFeatures::repaintAfterLayoutEnabled() && v) { 614 // FIXME: layoutDelta needs to be applied in parts before/after transforms and 615 // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 616 r.move(v->layoutDelta()); 617 } 618 619 mapRectToPaintInvalidationBacking(paintInvalidationContainer, r); 620 return r; 621 } 622 623 } 624