| 1 | /* |
| 2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| 3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| 4 | * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| 5 | * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com) |
| 6 | * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights reserved. |
| 7 | * Copyright (C) 2010 Google Inc. All rights reserved. |
| 8 | * |
| 9 | * This library is free software; you can redistribute it and/or |
| 10 | * modify it under the terms of the GNU Library General Public |
| 11 | * License as published by the Free Software Foundation; either |
| 12 | * version 2 of the License, or (at your option) any later version. |
| 13 | * |
| 14 | * This library is distributed in the hope that it will be useful, |
| 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 17 | * Library General Public License for more details. |
| 18 | * |
| 19 | * You should have received a copy of the GNU Library General Public License |
| 20 | * along with this library; see the file COPYING.LIB. If not, write to |
| 21 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 22 | * Boston, MA 02110-1301, USA. |
| 23 | * |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "RenderBoxModelObject.h" |
| 28 | |
| 29 | #include "BitmapImage.h" |
| 30 | #include "BorderEdge.h" |
| 31 | #include "CachedImage.h" |
| 32 | #include "Document.h" |
| 33 | #include "DocumentTimeline.h" |
| 34 | #include "FloatRoundedRect.h" |
| 35 | #include "Frame.h" |
| 36 | #include "FrameView.h" |
| 37 | #include "GeometryUtilities.h" |
| 38 | #include "GraphicsContext.h" |
| 39 | #include "HTMLFrameOwnerElement.h" |
| 40 | #include "HTMLFrameSetElement.h" |
| 41 | #include "HTMLImageElement.h" |
| 42 | #include "HTMLNames.h" |
| 43 | #include "ImageBuffer.h" |
| 44 | #include "ImageQualityController.h" |
| 45 | #include "Path.h" |
| 46 | #include "RenderBlock.h" |
| 47 | #include "RenderFlexibleBox.h" |
| 48 | #include "RenderFragmentContainer.h" |
| 49 | #include "RenderInline.h" |
| 50 | #include "RenderLayer.h" |
| 51 | #include "RenderLayerBacking.h" |
| 52 | #include "RenderLayerCompositor.h" |
| 53 | #include "RenderMultiColumnFlow.h" |
| 54 | #include "RenderTable.h" |
| 55 | #include "RenderTableRow.h" |
| 56 | #include "RenderText.h" |
| 57 | #include "RenderTextFragment.h" |
| 58 | #include "RenderTreeBuilder.h" |
| 59 | #include "RenderView.h" |
| 60 | #include "ScrollingConstraints.h" |
| 61 | #include "Settings.h" |
| 62 | #include "TransformState.h" |
| 63 | #include <wtf/IsoMallocInlines.h> |
| 64 | #include <wtf/NeverDestroyed.h> |
| 65 | #if !ASSERT_DISABLED |
| 66 | #include <wtf/SetForScope.h> |
| 67 | #endif |
| 68 | |
| 69 | #if PLATFORM(IOS_FAMILY) |
| 70 | #include "RuntimeApplicationChecks.h" |
| 71 | #endif |
| 72 | |
| 73 | namespace WebCore { |
| 74 | |
| 75 | using namespace HTMLNames; |
| 76 | |
| 77 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderBoxModelObject); |
| 78 | |
| 79 | // The HashMap for storing continuation pointers. |
| 80 | // An inline can be split with blocks occuring in between the inline content. |
| 81 | // When this occurs we need a pointer to the next object. We can basically be |
| 82 | // split into a sequence of inlines and blocks. The continuation will either be |
| 83 | // an anonymous block (that houses other blocks) or it will be an inline flow. |
| 84 | // <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as |
| 85 | // its continuation but the <b> will just have an inline as its continuation. |
| 86 | RenderBoxModelObject::ContinuationChainNode::ContinuationChainNode(RenderBoxModelObject& renderer) |
| 87 | : renderer(makeWeakPtr(renderer)) |
| 88 | { |
| 89 | } |
| 90 | |
| 91 | RenderBoxModelObject::ContinuationChainNode::~ContinuationChainNode() |
| 92 | { |
| 93 | if (next) { |
| 94 | ASSERT(previous); |
| 95 | ASSERT(next->previous == this); |
| 96 | next->previous = previous; |
| 97 | } |
| 98 | if (previous) { |
| 99 | ASSERT(previous->next == this); |
| 100 | previous->next = next; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | void RenderBoxModelObject::ContinuationChainNode::insertAfter(ContinuationChainNode& after) |
| 105 | { |
| 106 | ASSERT(!previous); |
| 107 | ASSERT(!next); |
| 108 | if ((next = after.next)) { |
| 109 | ASSERT(next->previous == &after); |
| 110 | next->previous = this; |
| 111 | } |
| 112 | previous = &after; |
| 113 | after.next = this; |
| 114 | } |
| 115 | |
| 116 | using ContinuationChainNodeMap = HashMap<const RenderBoxModelObject*, std::unique_ptr<RenderBoxModelObject::ContinuationChainNode>>; |
| 117 | |
| 118 | static ContinuationChainNodeMap& continuationChainNodeMap() |
| 119 | { |
| 120 | static NeverDestroyed<ContinuationChainNodeMap> map; |
| 121 | return map; |
| 122 | } |
| 123 | |
| 124 | using FirstLetterRemainingTextMap = HashMap<const RenderBoxModelObject*, WeakPtr<RenderTextFragment>>; |
| 125 | |
| 126 | static FirstLetterRemainingTextMap& firstLetterRemainingTextMap() |
| 127 | { |
| 128 | static NeverDestroyed<FirstLetterRemainingTextMap> map; |
| 129 | return map; |
| 130 | } |
| 131 | |
| 132 | void RenderBoxModelObject::setSelectionState(SelectionState state) |
| 133 | { |
| 134 | if (state == SelectionInside && selectionState() != SelectionNone) |
| 135 | return; |
| 136 | |
| 137 | if ((state == SelectionStart && selectionState() == SelectionEnd) |
| 138 | || (state == SelectionEnd && selectionState() == SelectionStart)) |
| 139 | RenderLayerModelObject::setSelectionState(SelectionBoth); |
| 140 | else |
| 141 | RenderLayerModelObject::setSelectionState(state); |
| 142 | |
| 143 | // FIXME: We should consider whether it is OK propagating to ancestor RenderInlines. |
| 144 | // This is a workaround for http://webkit.org/b/32123 |
| 145 | // The containing block can be null in case of an orphaned tree. |
| 146 | RenderBlock* containingBlock = this->containingBlock(); |
| 147 | if (containingBlock && !containingBlock->isRenderView()) |
| 148 | containingBlock->setSelectionState(state); |
| 149 | } |
| 150 | |
| 151 | void RenderBoxModelObject::contentChanged(ContentChangeType changeType) |
| 152 | { |
| 153 | if (!hasLayer()) |
| 154 | return; |
| 155 | |
| 156 | layer()->contentChanged(changeType); |
| 157 | } |
| 158 | |
| 159 | bool RenderBoxModelObject::hasAcceleratedCompositing() const |
| 160 | { |
| 161 | return view().compositor().hasAcceleratedCompositing(); |
| 162 | } |
| 163 | |
| 164 | RenderBoxModelObject::RenderBoxModelObject(Element& element, RenderStyle&& style, BaseTypeFlags baseTypeFlags) |
| 165 | : RenderLayerModelObject(element, WTFMove(style), baseTypeFlags | RenderBoxModelObjectFlag) |
| 166 | { |
| 167 | } |
| 168 | |
| 169 | RenderBoxModelObject::RenderBoxModelObject(Document& document, RenderStyle&& style, BaseTypeFlags baseTypeFlags) |
| 170 | : RenderLayerModelObject(document, WTFMove(style), baseTypeFlags | RenderBoxModelObjectFlag) |
| 171 | { |
| 172 | } |
| 173 | |
| 174 | RenderBoxModelObject::~RenderBoxModelObject() |
| 175 | { |
| 176 | // Do not add any code here. Add it to willBeDestroyed() instead. |
| 177 | ASSERT(!continuation()); |
| 178 | } |
| 179 | |
| 180 | void RenderBoxModelObject::willBeDestroyed() |
| 181 | { |
| 182 | if (hasContinuationChainNode()) |
| 183 | removeFromContinuationChain(); |
| 184 | |
| 185 | if (isFirstLetter()) |
| 186 | clearFirstLetterRemainingText(); |
| 187 | |
| 188 | if (!renderTreeBeingDestroyed()) |
| 189 | view().imageQualityController().rendererWillBeDestroyed(*this); |
| 190 | |
| 191 | RenderLayerModelObject::willBeDestroyed(); |
| 192 | } |
| 193 | |
| 194 | bool RenderBoxModelObject::hasVisibleBoxDecorationStyle() const |
| 195 | { |
| 196 | return hasBackground() || style().hasVisibleBorderDecoration() || style().hasAppearance() || style().boxShadow(); |
| 197 | } |
| 198 | |
| 199 | void RenderBoxModelObject::updateFromStyle() |
| 200 | { |
| 201 | RenderLayerModelObject::updateFromStyle(); |
| 202 | |
| 203 | // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, |
| 204 | // we only check for bits that could possibly be set to true. |
| 205 | const RenderStyle& styleToUse = style(); |
| 206 | setHasVisibleBoxDecorations(hasVisibleBoxDecorationStyle()); |
| 207 | setInline(styleToUse.isDisplayInlineType()); |
| 208 | setPositionState(styleToUse.position()); |
| 209 | setHorizontalWritingMode(styleToUse.isHorizontalWritingMode()); |
| 210 | if (styleToUse.isFlippedBlocksWritingMode()) |
| 211 | view().frameView().setHasFlippedBlockRenderers(true); |
| 212 | } |
| 213 | |
| 214 | static LayoutSize accumulateInFlowPositionOffsets(const RenderObject* child) |
| 215 | { |
| 216 | if (!child->isAnonymousBlock() || !child->isInFlowPositioned()) |
| 217 | return LayoutSize(); |
| 218 | LayoutSize offset; |
| 219 | for (RenderElement* parent = downcast<RenderBlock>(*child).inlineContinuation(); is<RenderInline>(parent); parent = parent->parent()) { |
| 220 | if (parent->isInFlowPositioned()) |
| 221 | offset += downcast<RenderInline>(*parent).offsetForInFlowPosition(); |
| 222 | } |
| 223 | return offset; |
| 224 | } |
| 225 | |
| 226 | static inline bool isOutOfFlowPositionedWithImplicitHeight(const RenderBoxModelObject& child) |
| 227 | { |
| 228 | return child.isOutOfFlowPositioned() && !child.style().logicalTop().isAuto() && !child.style().logicalBottom().isAuto(); |
| 229 | } |
| 230 | |
| 231 | RenderBlock* RenderBoxModelObject::containingBlockForAutoHeightDetection(Length logicalHeight) const |
| 232 | { |
| 233 | // For percentage heights: The percentage is calculated with respect to the |
| 234 | // height of the generated box's containing block. If the height of the |
| 235 | // containing block is not specified explicitly (i.e., it depends on content |
| 236 | // height), and this element is not absolutely positioned, the used height is |
| 237 | // calculated as if 'auto' was specified. |
| 238 | if (!logicalHeight.isPercentOrCalculated() || isOutOfFlowPositioned()) |
| 239 | return nullptr; |
| 240 | |
| 241 | // Anonymous block boxes are ignored when resolving percentage values that |
| 242 | // would refer to it: the closest non-anonymous ancestor box is used instead. |
| 243 | auto* cb = containingBlock(); |
| 244 | while (cb && cb->isAnonymous() && !is<RenderView>(cb)) |
| 245 | cb = cb->containingBlock(); |
| 246 | if (!cb) |
| 247 | return nullptr; |
| 248 | |
| 249 | // Matching RenderBox::percentageLogicalHeightIsResolvable() by |
| 250 | // ignoring table cell's attribute value, where it says that table cells |
| 251 | // violate what the CSS spec says to do with heights. Basically we don't care |
| 252 | // if the cell specified a height or not. |
| 253 | if (cb->isTableCell()) |
| 254 | return nullptr; |
| 255 | |
| 256 | // Match RenderBox::availableLogicalHeightUsing by special casing the layout |
| 257 | // view. The available height is taken from the frame. |
| 258 | if (cb->isRenderView()) |
| 259 | return nullptr; |
| 260 | |
| 261 | if (isOutOfFlowPositionedWithImplicitHeight(*cb)) |
| 262 | return nullptr; |
| 263 | |
| 264 | return cb; |
| 265 | } |
| 266 | |
| 267 | bool RenderBoxModelObject::hasAutoHeightOrContainingBlockWithAutoHeight() const |
| 268 | { |
| 269 | const auto* thisBox = isBox() ? downcast<RenderBox>(this) : nullptr; |
| 270 | Length logicalHeightLength = style().logicalHeight(); |
| 271 | auto* cb = containingBlockForAutoHeightDetection(logicalHeightLength); |
| 272 | |
| 273 | if (logicalHeightLength.isPercentOrCalculated() && cb && isBox()) |
| 274 | cb->addPercentHeightDescendant(*const_cast<RenderBox*>(downcast<RenderBox>(this))); |
| 275 | |
| 276 | if (thisBox && thisBox->isFlexItem()) { |
| 277 | auto& flexBox = downcast<RenderFlexibleBox>(*parent()); |
| 278 | if (flexBox.childLogicalHeightForPercentageResolution(*thisBox)) |
| 279 | return false; |
| 280 | } |
| 281 | |
| 282 | if (thisBox && thisBox->isGridItem() && thisBox->hasOverrideContainingBlockContentLogicalHeight()) |
| 283 | return false; |
| 284 | |
| 285 | if (logicalHeightLength.isAuto() && !isOutOfFlowPositionedWithImplicitHeight(*this)) |
| 286 | return true; |
| 287 | |
| 288 | if (document().inQuirksMode()) |
| 289 | return false; |
| 290 | |
| 291 | if (cb) |
| 292 | return !cb->hasDefiniteLogicalHeight(); |
| 293 | |
| 294 | return false; |
| 295 | } |
| 296 | |
| 297 | DecodingMode RenderBoxModelObject::decodingModeForImageDraw(const Image& image, const PaintInfo& paintInfo) const |
| 298 | { |
| 299 | if (!is<BitmapImage>(image)) |
| 300 | return DecodingMode::Synchronous; |
| 301 | |
| 302 | const BitmapImage& bitmapImage = downcast<BitmapImage>(image); |
| 303 | if (bitmapImage.canAnimate()) { |
| 304 | // The DecodingMode for the current frame has to be Synchronous. The DecodingMode |
| 305 | // for the next frame will be calculated in BitmapImage::internalStartAnimation(). |
| 306 | return DecodingMode::Synchronous; |
| 307 | } |
| 308 | |
| 309 | // Large image case. |
| 310 | #if PLATFORM(IOS_FAMILY) |
| 311 | if (IOSApplication::isIBooksStorytime()) |
| 312 | return DecodingMode::Synchronous; |
| 313 | #endif |
| 314 | if (is<HTMLImageElement>(element())) { |
| 315 | auto decodingMode = downcast<HTMLImageElement>(*element()).decodingMode(); |
| 316 | if (decodingMode != DecodingMode::Auto) |
| 317 | return decodingMode; |
| 318 | } |
| 319 | if (bitmapImage.isLargeImageAsyncDecodingEnabledForTesting()) |
| 320 | return DecodingMode::Asynchronous; |
| 321 | if (document().isImageDocument()) |
| 322 | return DecodingMode::Synchronous; |
| 323 | if (paintInfo.paintBehavior.contains(PaintBehavior::Snapshotting)) |
| 324 | return DecodingMode::Synchronous; |
| 325 | if (!settings().largeImageAsyncDecodingEnabled()) |
| 326 | return DecodingMode::Synchronous; |
| 327 | if (!bitmapImage.canUseAsyncDecodingForLargeImages()) |
| 328 | return DecodingMode::Synchronous; |
| 329 | if (paintInfo.paintBehavior.contains(PaintBehavior::TileFirstPaint)) |
| 330 | return DecodingMode::Asynchronous; |
| 331 | // FIXME: isVisibleInViewport() is not cheap. Find a way to make this condition faster. |
| 332 | if (!isVisibleInViewport()) |
| 333 | return DecodingMode::Asynchronous; |
| 334 | return DecodingMode::Synchronous; |
| 335 | } |
| 336 | |
| 337 | LayoutSize RenderBoxModelObject::relativePositionOffset() const |
| 338 | { |
| 339 | // This function has been optimized to avoid calls to containingBlock() in the common case |
| 340 | // where all values are either auto or fixed. |
| 341 | |
| 342 | LayoutSize offset = accumulateInFlowPositionOffsets(this); |
| 343 | |
| 344 | // Objects that shrink to avoid floats normally use available line width when computing containing block width. However |
| 345 | // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the |
| 346 | // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly |
| 347 | // call availableWidth on our containing block. |
| 348 | // However for grid items the containing block is the grid area, so offsets should be resolved against that: |
| 349 | // https://drafts.csswg.org/css-grid/#grid-item-sizing |
| 350 | if (!style().left().isAuto() || !style().right().isAuto()) { |
| 351 | LayoutUnit availableWidth = hasOverrideContainingBlockContentWidth() |
| 352 | ? overrideContainingBlockContentWidth().valueOr(LayoutUnit()) : containingBlock()->availableWidth(); |
| 353 | if (!style().left().isAuto()) { |
| 354 | if (!style().right().isAuto() && !containingBlock()->style().isLeftToRightDirection()) |
| 355 | offset.setWidth(-valueForLength(style().right(), !style().right().isFixed() ? availableWidth : 0_lu)); |
| 356 | else |
| 357 | offset.expand(valueForLength(style().left(), !style().left().isFixed() ? availableWidth : 0_lu), 0_lu); |
| 358 | } else if (!style().right().isAuto()) |
| 359 | offset.expand(-valueForLength(style().right(), !style().right().isFixed() ? availableWidth : 0_lu), 0_lu); |
| 360 | } |
| 361 | |
| 362 | // If the containing block of a relatively positioned element does not |
| 363 | // specify a height, a percentage top or bottom offset should be resolved as |
| 364 | // auto. An exception to this is if the containing block has the WinIE quirk |
| 365 | // where <html> and <body> assume the size of the viewport. In this case, |
| 366 | // calculate the percent offset based on this height. |
| 367 | // See <https://bugs.webkit.org/show_bug.cgi?id=26396>. |
| 368 | // Another exception is a grid item, as the containing block is the grid area: |
| 369 | // https://drafts.csswg.org/css-grid/#grid-item-sizing |
| 370 | if (!style().top().isAuto() |
| 371 | && (!style().top().isPercentOrCalculated() |
| 372 | || !containingBlock()->hasAutoHeightOrContainingBlockWithAutoHeight() |
| 373 | || containingBlock()->stretchesToViewport() |
| 374 | || hasOverrideContainingBlockContentHeight())) { |
| 375 | // FIXME: The computation of the available height is repeated later for "bottom". |
| 376 | // We could refactor this and move it to some common code for both ifs, however moving it outside of the ifs |
| 377 | // is not possible as it'd cause performance regressions. |
| 378 | offset.expand(0_lu, valueForLength(style().top(), !style().top().isFixed() |
| 379 | ? (hasOverrideContainingBlockContentHeight() ? overrideContainingBlockContentHeight().valueOr(0_lu) : containingBlock()->availableHeight()) |
| 380 | : LayoutUnit())); |
| 381 | } else if (!style().bottom().isAuto() |
| 382 | && (!style().bottom().isPercentOrCalculated() |
| 383 | || !containingBlock()->hasAutoHeightOrContainingBlockWithAutoHeight() |
| 384 | || containingBlock()->stretchesToViewport() |
| 385 | || hasOverrideContainingBlockContentHeight())) { |
| 386 | // FIXME: Check comment above for "top", it applies here too. |
| 387 | offset.expand(0_lu, -valueForLength(style().bottom(), !style().bottom().isFixed() |
| 388 | ? (hasOverrideContainingBlockContentHeight() ? overrideContainingBlockContentHeight().valueOr(0_lu) : containingBlock()->availableHeight()) |
| 389 | : LayoutUnit())); |
| 390 | } |
| 391 | |
| 392 | return offset; |
| 393 | } |
| 394 | |
| 395 | LayoutPoint RenderBoxModelObject::adjustedPositionRelativeToOffsetParent(const LayoutPoint& startPoint) const |
| 396 | { |
| 397 | // If the element is the HTML body element or doesn't have a parent |
| 398 | // return 0 and stop this algorithm. |
| 399 | if (isBody() || !parent()) |
| 400 | return LayoutPoint(); |
| 401 | |
| 402 | LayoutPoint referencePoint = startPoint; |
| 403 | |
| 404 | // If the offsetParent of the element is null, or is the HTML body element, |
| 405 | // return the distance between the canvas origin and the left border edge |
| 406 | // of the element and stop this algorithm. |
| 407 | if (const RenderBoxModelObject* offsetParent = this->offsetParent()) { |
| 408 | if (is<RenderBox>(*offsetParent) && !offsetParent->isBody() && !is<RenderTable>(*offsetParent)) |
| 409 | referencePoint.move(-downcast<RenderBox>(*offsetParent).borderLeft(), -downcast<RenderBox>(*offsetParent).borderTop()); |
| 410 | if (!isOutOfFlowPositioned() || enclosingFragmentedFlow()) { |
| 411 | if (isRelativelyPositioned()) |
| 412 | referencePoint.move(relativePositionOffset()); |
| 413 | else if (isStickilyPositioned()) |
| 414 | referencePoint.move(stickyPositionOffset()); |
| 415 | |
| 416 | // CSS regions specification says that region flows should return the body element as their offsetParent. |
| 417 | // Since we will bypass the body’s renderer anyway, just end the loop if we encounter a region flow (named flow thread). |
| 418 | // See http://dev.w3.org/csswg/css-regions/#cssomview-offset-attributes |
| 419 | auto* ancestor = parent(); |
| 420 | while (ancestor != offsetParent) { |
| 421 | // FIXME: What are we supposed to do inside SVG content? |
| 422 | |
| 423 | if (is<RenderMultiColumnFlow>(*ancestor)) { |
| 424 | // We need to apply a translation based off what region we are inside. |
| 425 | RenderFragmentContainer* fragment = downcast<RenderMultiColumnFlow>(*ancestor).physicalTranslationFromFlowToFragment(referencePoint); |
| 426 | if (fragment) |
| 427 | referencePoint.moveBy(fragment->topLeftLocation()); |
| 428 | } else if (!isOutOfFlowPositioned()) { |
| 429 | if (is<RenderBox>(*ancestor) && !is<RenderTableRow>(*ancestor)) |
| 430 | referencePoint.moveBy(downcast<RenderBox>(*ancestor).topLeftLocation()); |
| 431 | } |
| 432 | |
| 433 | ancestor = ancestor->parent(); |
| 434 | } |
| 435 | |
| 436 | if (is<RenderBox>(*offsetParent) && offsetParent->isBody() && !offsetParent->isPositioned()) |
| 437 | referencePoint.moveBy(downcast<RenderBox>(*offsetParent).topLeftLocation()); |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | return referencePoint; |
| 442 | } |
| 443 | |
| 444 | void RenderBoxModelObject::computeStickyPositionConstraints(StickyPositionViewportConstraints& constraints, const FloatRect& constrainingRect) const |
| 445 | { |
| 446 | constraints.setConstrainingRectAtLastLayout(constrainingRect); |
| 447 | |
| 448 | RenderBlock* containingBlock = this->containingBlock(); |
| 449 | RenderLayer* enclosingClippingLayer = layer()->enclosingOverflowClipLayer(ExcludeSelf); |
| 450 | RenderBox& enclosingClippingBox = enclosingClippingLayer ? downcast<RenderBox>(enclosingClippingLayer->renderer()) : view(); |
| 451 | |
| 452 | LayoutRect containerContentRect; |
| 453 | if (!enclosingClippingLayer || (containingBlock != &enclosingClippingBox)) |
| 454 | containerContentRect = containingBlock->contentBoxRect(); |
| 455 | else { |
| 456 | containerContentRect = containingBlock->layoutOverflowRect(); |
| 457 | LayoutPoint containerLocation = containerContentRect.location() + LayoutPoint(containingBlock->borderLeft() + containingBlock->paddingLeft(), |
| 458 | containingBlock->borderTop() + containingBlock->paddingTop()); |
| 459 | containerContentRect.setLocation(containerLocation); |
| 460 | } |
| 461 | |
| 462 | LayoutUnit maxWidth = containingBlock->availableLogicalWidth(); |
| 463 | |
| 464 | // Sticky positioned element ignore any override logical width on the containing block (as they don't call |
| 465 | // containingBlockLogicalWidthForContent). It's unclear whether this is totally fine. |
| 466 | LayoutBoxExtent minMargin(minimumValueForLength(style().marginTop(), maxWidth), |
| 467 | minimumValueForLength(style().marginRight(), maxWidth), |
| 468 | minimumValueForLength(style().marginBottom(), maxWidth), |
| 469 | minimumValueForLength(style().marginLeft(), maxWidth)); |
| 470 | |
| 471 | // Compute the container-relative area within which the sticky element is allowed to move. |
| 472 | containerContentRect.contract(minMargin); |
| 473 | |
| 474 | // Finally compute container rect relative to the scrolling ancestor. |
| 475 | FloatRect containerRectRelativeToScrollingAncestor = containingBlock->localToContainerQuad(FloatRect(containerContentRect), &enclosingClippingBox).boundingBox(); |
| 476 | if (enclosingClippingLayer) { |
| 477 | FloatPoint containerLocationRelativeToScrollingAncestor = containerRectRelativeToScrollingAncestor.location() - |
| 478 | FloatSize(enclosingClippingBox.borderLeft() + enclosingClippingBox.paddingLeft(), |
| 479 | enclosingClippingBox.borderTop() + enclosingClippingBox.paddingTop()); |
| 480 | if (&enclosingClippingBox != containingBlock) |
| 481 | containerLocationRelativeToScrollingAncestor += enclosingClippingLayer->scrollOffset(); |
| 482 | containerRectRelativeToScrollingAncestor.setLocation(containerLocationRelativeToScrollingAncestor); |
| 483 | } |
| 484 | constraints.setContainingBlockRect(containerRectRelativeToScrollingAncestor); |
| 485 | |
| 486 | // Now compute the sticky box rect, also relative to the scrolling ancestor. |
| 487 | LayoutRect stickyBoxRect = frameRectForStickyPositioning(); |
| 488 | LayoutRect flippedStickyBoxRect = stickyBoxRect; |
| 489 | containingBlock->flipForWritingMode(flippedStickyBoxRect); |
| 490 | FloatRect stickyBoxRelativeToScrollingAnecstor = flippedStickyBoxRect; |
| 491 | |
| 492 | // FIXME: sucks to call localToContainerQuad again, but we can't just offset from the previously computed rect if there are transforms. |
| 493 | // Map to the view to avoid including page scale factor. |
| 494 | FloatPoint stickyLocationRelativeToScrollingAncestor = flippedStickyBoxRect.location() + containingBlock->localToContainerQuad(FloatRect(FloatPoint(), containingBlock->size()), &enclosingClippingBox).boundingBox().location(); |
| 495 | if (enclosingClippingLayer) { |
| 496 | stickyLocationRelativeToScrollingAncestor -= FloatSize(enclosingClippingBox.borderLeft() + enclosingClippingBox.paddingLeft(), |
| 497 | enclosingClippingBox.borderTop() + enclosingClippingBox.paddingTop()); |
| 498 | if (&enclosingClippingBox != containingBlock) |
| 499 | stickyLocationRelativeToScrollingAncestor += enclosingClippingLayer->scrollOffset(); |
| 500 | } |
| 501 | // FIXME: For now, assume that |this| is not transformed. |
| 502 | stickyBoxRelativeToScrollingAnecstor.setLocation(stickyLocationRelativeToScrollingAncestor); |
| 503 | constraints.setStickyBoxRect(stickyBoxRelativeToScrollingAnecstor); |
| 504 | |
| 505 | if (!style().left().isAuto()) { |
| 506 | constraints.setLeftOffset(valueForLength(style().left(), constrainingRect.width())); |
| 507 | constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeLeft); |
| 508 | } |
| 509 | |
| 510 | if (!style().right().isAuto()) { |
| 511 | constraints.setRightOffset(valueForLength(style().right(), constrainingRect.width())); |
| 512 | constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeRight); |
| 513 | } |
| 514 | |
| 515 | if (!style().top().isAuto()) { |
| 516 | constraints.setTopOffset(valueForLength(style().top(), constrainingRect.height())); |
| 517 | constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeTop); |
| 518 | } |
| 519 | |
| 520 | if (!style().bottom().isAuto()) { |
| 521 | constraints.setBottomOffset(valueForLength(style().bottom(), constrainingRect.height())); |
| 522 | constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeBottom); |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | FloatRect RenderBoxModelObject::constrainingRectForStickyPosition() const |
| 527 | { |
| 528 | RenderLayer* enclosingClippingLayer = layer()->enclosingOverflowClipLayer(ExcludeSelf); |
| 529 | if (enclosingClippingLayer) { |
| 530 | RenderBox& enclosingClippingBox = downcast<RenderBox>(enclosingClippingLayer->renderer()); |
| 531 | LayoutRect clipRect = enclosingClippingBox.overflowClipRect(LayoutPoint(), nullptr); // FIXME: make this work in regions. |
| 532 | clipRect.contract(LayoutSize(enclosingClippingBox.paddingLeft() + enclosingClippingBox.paddingRight(), |
| 533 | enclosingClippingBox.paddingTop() + enclosingClippingBox.paddingBottom())); |
| 534 | |
| 535 | FloatRect constrainingRect = enclosingClippingBox.localToContainerQuad(FloatRect(clipRect), &view()).boundingBox(); |
| 536 | |
| 537 | FloatPoint scrollOffset = FloatPoint() + enclosingClippingLayer->scrollOffset(); |
| 538 | |
| 539 | float scrollbarOffset = 0; |
| 540 | if (enclosingClippingBox.hasLayer() && enclosingClippingBox.shouldPlaceBlockDirectionScrollbarOnLeft()) |
| 541 | scrollbarOffset = enclosingClippingBox.layer()->verticalScrollbarWidth(IgnoreOverlayScrollbarSize); |
| 542 | |
| 543 | constrainingRect.setLocation(FloatPoint(scrollOffset.x() + scrollbarOffset, scrollOffset.y())); |
| 544 | return constrainingRect; |
| 545 | } |
| 546 | |
| 547 | return view().frameView().rectForFixedPositionLayout(); |
| 548 | } |
| 549 | |
| 550 | LayoutSize RenderBoxModelObject::stickyPositionOffset() const |
| 551 | { |
| 552 | ASSERT(hasLayer()); |
| 553 | |
| 554 | FloatRect constrainingRect = constrainingRectForStickyPosition(); |
| 555 | StickyPositionViewportConstraints constraints; |
| 556 | computeStickyPositionConstraints(constraints, constrainingRect); |
| 557 | |
| 558 | // The sticky offset is physical, so we can just return the delta computed in absolute coords (though it may be wrong with transforms). |
| 559 | return LayoutSize(constraints.computeStickyOffset(constrainingRect)); |
| 560 | } |
| 561 | |
| 562 | LayoutSize RenderBoxModelObject::offsetForInFlowPosition() const |
| 563 | { |
| 564 | if (isRelativelyPositioned()) |
| 565 | return relativePositionOffset(); |
| 566 | |
| 567 | if (isStickilyPositioned()) |
| 568 | return stickyPositionOffset(); |
| 569 | |
| 570 | return LayoutSize(); |
| 571 | } |
| 572 | |
| 573 | LayoutUnit RenderBoxModelObject::offsetLeft() const |
| 574 | { |
| 575 | // Note that RenderInline and RenderBox override this to pass a different |
| 576 | // startPoint to adjustedPositionRelativeToOffsetParent. |
| 577 | return adjustedPositionRelativeToOffsetParent(LayoutPoint()).x(); |
| 578 | } |
| 579 | |
| 580 | LayoutUnit RenderBoxModelObject::offsetTop() const |
| 581 | { |
| 582 | // Note that RenderInline and RenderBox override this to pass a different |
| 583 | // startPoint to adjustedPositionRelativeToOffsetParent. |
| 584 | return adjustedPositionRelativeToOffsetParent(LayoutPoint()).y(); |
| 585 | } |
| 586 | |
| 587 | LayoutUnit RenderBoxModelObject::computedCSSPadding(const Length& padding) const |
| 588 | { |
| 589 | LayoutUnit w; |
| 590 | if (padding.isPercentOrCalculated()) |
| 591 | w = containingBlockLogicalWidthForContent(); |
| 592 | return minimumValueForLength(padding, w); |
| 593 | } |
| 594 | |
| 595 | RoundedRect RenderBoxModelObject::getBackgroundRoundedRect(const LayoutRect& borderRect, InlineFlowBox* box, LayoutUnit inlineBoxWidth, LayoutUnit inlineBoxHeight, |
| 596 | bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const |
| 597 | { |
| 598 | RoundedRect border = style().getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 599 | if (box && (box->nextLineBox() || box->prevLineBox())) { |
| 600 | RoundedRect segmentBorder = style().getRoundedBorderFor(LayoutRect(0_lu, 0_lu, inlineBoxWidth, inlineBoxHeight), includeLogicalLeftEdge, includeLogicalRightEdge); |
| 601 | border.setRadii(segmentBorder.radii()); |
| 602 | } |
| 603 | return border; |
| 604 | } |
| 605 | |
| 606 | void RenderBoxModelObject::clipRoundedInnerRect(GraphicsContext& context, const FloatRect& rect, const FloatRoundedRect& clipRect) |
| 607 | { |
| 608 | if (clipRect.isRenderable()) |
| 609 | context.clipRoundedRect(clipRect); |
| 610 | else { |
| 611 | // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together. |
| 612 | if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) { |
| 613 | FloatRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y()); |
| 614 | FloatRoundedRect::Radii topCornerRadii; |
| 615 | topCornerRadii.setTopLeft(clipRect.radii().topLeft()); |
| 616 | context.clipRoundedRect(FloatRoundedRect(topCorner, topCornerRadii)); |
| 617 | |
| 618 | FloatRect bottomCorner(rect.x(), rect.y(), clipRect.rect().maxX() - rect.x(), clipRect.rect().maxY() - rect.y()); |
| 619 | FloatRoundedRect::Radii bottomCornerRadii; |
| 620 | bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight()); |
| 621 | context.clipRoundedRect(FloatRoundedRect(bottomCorner, bottomCornerRadii)); |
| 622 | } |
| 623 | |
| 624 | if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) { |
| 625 | FloatRect topCorner(rect.x(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x(), rect.maxY() - clipRect.rect().y()); |
| 626 | FloatRoundedRect::Radii topCornerRadii; |
| 627 | topCornerRadii.setTopRight(clipRect.radii().topRight()); |
| 628 | context.clipRoundedRect(FloatRoundedRect(topCorner, topCornerRadii)); |
| 629 | |
| 630 | FloatRect bottomCorner(clipRect.rect().x(), rect.y(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y()); |
| 631 | FloatRoundedRect::Radii bottomCornerRadii; |
| 632 | bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft()); |
| 633 | context.clipRoundedRect(FloatRoundedRect(bottomCorner, bottomCornerRadii)); |
| 634 | } |
| 635 | } |
| 636 | } |
| 637 | |
| 638 | static LayoutRect shrinkRectByOneDevicePixel(const GraphicsContext& context, const LayoutRect& rect, float devicePixelRatio) |
| 639 | { |
| 640 | LayoutRect shrunkRect = rect; |
| 641 | AffineTransform transform = context.getCTM(); |
| 642 | shrunkRect.inflateX(-ceilToDevicePixel(1_lu / transform.xScale(), devicePixelRatio)); |
| 643 | shrunkRect.inflateY(-ceilToDevicePixel(1_lu / transform.yScale(), devicePixelRatio)); |
| 644 | return shrunkRect; |
| 645 | } |
| 646 | |
| 647 | LayoutRect RenderBoxModelObject::borderInnerRectAdjustedForBleedAvoidance(const GraphicsContext& context, const LayoutRect& rect, BackgroundBleedAvoidance bleedAvoidance) const |
| 648 | { |
| 649 | if (bleedAvoidance != BackgroundBleedBackgroundOverBorder) |
| 650 | return rect; |
| 651 | |
| 652 | // We shrink the rectangle by one device pixel on each side to make it fully overlap the anti-aliased background border |
| 653 | return shrinkRectByOneDevicePixel(context, rect, document().deviceScaleFactor()); |
| 654 | } |
| 655 | |
| 656 | RoundedRect RenderBoxModelObject::backgroundRoundedRectAdjustedForBleedAvoidance(const GraphicsContext& context, const LayoutRect& borderRect, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const |
| 657 | { |
| 658 | if (bleedAvoidance == BackgroundBleedShrinkBackground) { |
| 659 | // We shrink the rectangle by one device pixel on each side because the bleed is one pixel maximum. |
| 660 | return getBackgroundRoundedRect(shrinkRectByOneDevicePixel(context, borderRect, document().deviceScaleFactor()), box, boxSize.width(), boxSize.height(), |
| 661 | includeLogicalLeftEdge, includeLogicalRightEdge); |
| 662 | } |
| 663 | if (bleedAvoidance == BackgroundBleedBackgroundOverBorder) |
| 664 | return style().getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 665 | |
| 666 | return getBackgroundRoundedRect(borderRect, box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge); |
| 667 | } |
| 668 | |
| 669 | static void applyBoxShadowForBackground(GraphicsContext& context, const RenderStyle& style) |
| 670 | { |
| 671 | const ShadowData* boxShadow = style.boxShadow(); |
| 672 | while (boxShadow->style() != Normal) |
| 673 | boxShadow = boxShadow->next(); |
| 674 | |
| 675 | FloatSize shadowOffset(boxShadow->x(), boxShadow->y()); |
| 676 | if (!boxShadow->isWebkitBoxShadow()) |
| 677 | context.setShadow(shadowOffset, boxShadow->radius(), style.colorByApplyingColorFilter(boxShadow->color())); |
| 678 | else |
| 679 | context.setLegacyShadow(shadowOffset, boxShadow->radius(), style.colorByApplyingColorFilter(boxShadow->color())); |
| 680 | } |
| 681 | |
| 682 | InterpolationQuality RenderBoxModelObject::chooseInterpolationQuality(GraphicsContext& context, Image& image, const void* layer, const LayoutSize& size) |
| 683 | { |
| 684 | return view().imageQualityController().chooseInterpolationQuality(context, this, image, layer, size); |
| 685 | } |
| 686 | |
| 687 | void RenderBoxModelObject::paintMaskForTextFillBox(ImageBuffer* maskImage, const IntRect& maskRect, InlineFlowBox* box, const LayoutRect& scrolledPaintRect) |
| 688 | { |
| 689 | GraphicsContext& maskImageContext = maskImage->context(); |
| 690 | maskImageContext.translate(-maskRect.location()); |
| 691 | |
| 692 | // Now add the text to the clip. We do this by painting using a special paint phase that signals to |
| 693 | // InlineTextBoxes that they should just add their contents to the clip. |
| 694 | PaintInfo info(maskImageContext, maskRect, PaintPhase::TextClip, PaintBehavior::ForceBlackText); |
| 695 | if (box) { |
| 696 | const RootInlineBox& rootBox = box->root(); |
| 697 | box->paint(info, LayoutPoint(scrolledPaintRect.x() - box->x(), scrolledPaintRect.y() - box->y()), rootBox.lineTop(), rootBox.lineBottom()); |
| 698 | } else { |
| 699 | LayoutSize localOffset = is<RenderBox>(*this) ? downcast<RenderBox>(*this).locationOffset() : LayoutSize(); |
| 700 | paint(info, scrolledPaintRect.location() - localOffset); |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer& bgLayer, const LayoutRect& rect, |
| 705 | BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, CompositeOperator op, RenderElement* backgroundObject, BaseBackgroundColorUsage baseBgColorUsage) |
| 706 | { |
| 707 | GraphicsContext& context = paintInfo.context(); |
| 708 | if (context.paintingDisabled() || rect.isEmpty()) |
| 709 | return; |
| 710 | |
| 711 | bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; |
| 712 | bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; |
| 713 | |
| 714 | bool hasRoundedBorder = style().hasBorderRadius() && (includeLeftEdge || includeRightEdge); |
| 715 | bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer.attachment() == FillAttachment::LocalBackground; |
| 716 | bool isBorderFill = bgLayer.clip() == FillBox::Border; |
| 717 | bool isRoot = this->isDocumentElementRenderer(); |
| 718 | |
| 719 | Color bgColor = color; |
| 720 | StyleImage* bgImage = bgLayer.image(); |
| 721 | bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(this, style().effectiveZoom()); |
| 722 | |
| 723 | if (context.invalidatingImagesWithAsyncDecodes()) { |
| 724 | if (shouldPaintBackgroundImage && bgImage->cachedImage()->isClientWaitingForAsyncDecoding(*this)) |
| 725 | bgImage->cachedImage()->removeAllClientsWaitingForAsyncDecoding(); |
| 726 | return; |
| 727 | } |
| 728 | |
| 729 | bool forceBackgroundToWhite = false; |
| 730 | if (document().printing()) { |
| 731 | if (style().printColorAdjust() == PrintColorAdjust::Economy) |
| 732 | forceBackgroundToWhite = true; |
| 733 | if (settings().shouldPrintBackgrounds()) |
| 734 | forceBackgroundToWhite = false; |
| 735 | } |
| 736 | |
| 737 | // When printing backgrounds is disabled or using economy mode, |
| 738 | // change existing background colors and images to a solid white background. |
| 739 | // If there's no bg color or image, leave it untouched to avoid affecting transparency. |
| 740 | // We don't try to avoid loading the background images, because this style flag is only set |
| 741 | // when printing, and at that point we've already loaded the background images anyway. (To avoid |
| 742 | // loading the background images we'd have to do this check when applying styles rather than |
| 743 | // while rendering.) |
| 744 | if (forceBackgroundToWhite) { |
| 745 | // Note that we can't reuse this variable below because the bgColor might be changed |
| 746 | bool shouldPaintBackgroundColor = !bgLayer.next() && bgColor.isVisible(); |
| 747 | if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { |
| 748 | bgColor = Color::white; |
| 749 | shouldPaintBackgroundImage = false; |
| 750 | } |
| 751 | } |
| 752 | |
| 753 | bool baseBgColorOnly = (baseBgColorUsage == BaseBackgroundColorOnly); |
| 754 | if (baseBgColorOnly && (!isRoot || bgLayer.next() || bgColor.isOpaque())) |
| 755 | return; |
| 756 | |
| 757 | bool colorVisible = bgColor.isVisible(); |
| 758 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 759 | FloatRect pixelSnappedRect = snapRectToDevicePixels(rect, deviceScaleFactor); |
| 760 | |
| 761 | // Fast path for drawing simple color backgrounds. |
| 762 | if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill && !bgLayer.next()) { |
| 763 | if (!colorVisible) |
| 764 | return; |
| 765 | |
| 766 | bool boxShadowShouldBeAppliedToBackground = this->boxShadowShouldBeAppliedToBackground(rect.location(), bleedAvoidance, box); |
| 767 | GraphicsContextStateSaver shadowStateSaver(context, boxShadowShouldBeAppliedToBackground); |
| 768 | if (boxShadowShouldBeAppliedToBackground) |
| 769 | applyBoxShadowForBackground(context, style()); |
| 770 | |
| 771 | if (hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer) { |
| 772 | FloatRoundedRect pixelSnappedBorder = backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, |
| 773 | includeLeftEdge, includeRightEdge).pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 774 | if (pixelSnappedBorder.isRenderable()) { |
| 775 | CompositeOperator previousOperator = context.compositeOperation(); |
| 776 | bool saveRestoreCompositeOp = op != previousOperator; |
| 777 | if (saveRestoreCompositeOp) |
| 778 | context.setCompositeOperation(op); |
| 779 | |
| 780 | context.fillRoundedRect(pixelSnappedBorder, bgColor); |
| 781 | |
| 782 | if (saveRestoreCompositeOp) |
| 783 | context.setCompositeOperation(previousOperator); |
| 784 | } else { |
| 785 | context.save(); |
| 786 | clipRoundedInnerRect(context, pixelSnappedRect, pixelSnappedBorder); |
| 787 | context.fillRect(pixelSnappedBorder.rect(), bgColor, op); |
| 788 | context.restore(); |
| 789 | } |
| 790 | } else |
| 791 | context.fillRect(pixelSnappedRect, bgColor, op); |
| 792 | |
| 793 | return; |
| 794 | } |
| 795 | |
| 796 | // FillBox::Border radius clipping is taken care of by BackgroundBleedUseTransparencyLayer |
| 797 | bool clipToBorderRadius = hasRoundedBorder && !(isBorderFill && bleedAvoidance == BackgroundBleedUseTransparencyLayer); |
| 798 | GraphicsContextStateSaver clipToBorderStateSaver(context, clipToBorderRadius); |
| 799 | if (clipToBorderRadius) { |
| 800 | RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) : getBackgroundRoundedRect(rect, box, boxSize.width(), boxSize.height(), includeLeftEdge, includeRightEdge); |
| 801 | |
| 802 | // Clip to the padding or content boxes as necessary. |
| 803 | if (bgLayer.clip() == FillBox::Content) { |
| 804 | border = style().getRoundedInnerBorderFor(border.rect(), |
| 805 | paddingTop() + borderTop(), paddingBottom() + borderBottom(), paddingLeft() + borderLeft(), paddingRight() + borderRight(), includeLeftEdge, includeRightEdge); |
| 806 | } else if (bgLayer.clip() == FillBox::Padding) |
| 807 | border = style().getRoundedInnerBorderFor(border.rect(), includeLeftEdge, includeRightEdge); |
| 808 | |
| 809 | clipRoundedInnerRect(context, pixelSnappedRect, border.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); |
| 810 | } |
| 811 | |
| 812 | LayoutUnit bLeft = includeLeftEdge ? borderLeft() : 0_lu; |
| 813 | LayoutUnit bRight = includeRightEdge ? borderRight() : 0_lu; |
| 814 | LayoutUnit pLeft = includeLeftEdge ? paddingLeft() : 0_lu; |
| 815 | LayoutUnit pRight = includeRightEdge ? paddingRight() : 0_lu; |
| 816 | |
| 817 | GraphicsContextStateSaver clipWithScrollingStateSaver(context, clippedWithLocalScrolling); |
| 818 | LayoutRect scrolledPaintRect = rect; |
| 819 | if (clippedWithLocalScrolling) { |
| 820 | // Clip to the overflow area. |
| 821 | auto& thisBox = downcast<RenderBox>(*this); |
| 822 | context.clip(thisBox.overflowClipRect(rect.location())); |
| 823 | |
| 824 | // Adjust the paint rect to reflect a scrolled content box with borders at the ends. |
| 825 | scrolledPaintRect.moveBy(-thisBox.scrollPosition()); |
| 826 | scrolledPaintRect.setWidth(bLeft + layer()->scrollWidth() + bRight); |
| 827 | scrolledPaintRect.setHeight(borderTop() + layer()->scrollHeight() + borderBottom()); |
| 828 | } |
| 829 | |
| 830 | GraphicsContextStateSaver backgroundClipStateSaver(context, false); |
| 831 | std::unique_ptr<ImageBuffer> maskImage; |
| 832 | IntRect maskRect; |
| 833 | |
| 834 | if (bgLayer.clip() == FillBox::Padding || bgLayer.clip() == FillBox::Content) { |
| 835 | // Clip to the padding or content boxes as necessary. |
| 836 | if (!clipToBorderRadius) { |
| 837 | bool includePadding = bgLayer.clip() == FillBox::Content; |
| 838 | LayoutRect clipRect = LayoutRect(scrolledPaintRect.x() + bLeft + (includePadding ? pLeft : 0_lu), |
| 839 | scrolledPaintRect.y() + borderTop() + (includePadding ? paddingTop() : 0_lu), |
| 840 | scrolledPaintRect.width() - bLeft - bRight - (includePadding ? pLeft + pRight : 0_lu), |
| 841 | scrolledPaintRect.height() - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0_lu)); |
| 842 | backgroundClipStateSaver.save(); |
| 843 | context.clip(clipRect); |
| 844 | } |
| 845 | } else if (bgLayer.clip() == FillBox::Text) { |
| 846 | // We have to draw our text into a mask that can then be used to clip background drawing. |
| 847 | // First figure out how big the mask has to be. It should be no bigger than what we need |
| 848 | // to actually render, so we should intersect the dirty rect with the border box of the background. |
| 849 | maskRect = snappedIntRect(rect); |
| 850 | maskRect.intersect(snappedIntRect(paintInfo.rect)); |
| 851 | |
| 852 | // Now create the mask. |
| 853 | maskImage = ImageBuffer::createCompatibleBuffer(maskRect.size(), ColorSpaceSRGB, context); |
| 854 | if (!maskImage) |
| 855 | return; |
| 856 | paintMaskForTextFillBox(maskImage.get(), maskRect, box, scrolledPaintRect); |
| 857 | |
| 858 | // The mask has been created. Now we just need to clip to it. |
| 859 | backgroundClipStateSaver.save(); |
| 860 | context.clip(maskRect); |
| 861 | context.beginTransparencyLayer(1); |
| 862 | } |
| 863 | |
| 864 | // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with |
| 865 | // no background in the child document should show the parent's background. |
| 866 | bool isOpaqueRoot = false; |
| 867 | if (isRoot) { |
| 868 | isOpaqueRoot = true; |
| 869 | if (!bgLayer.next() && !bgColor.isOpaque()) { |
| 870 | HTMLFrameOwnerElement* ownerElement = document().ownerElement(); |
| 871 | if (ownerElement) { |
| 872 | if (!ownerElement->hasTagName(frameTag)) { |
| 873 | // Locate the <body> element using the DOM. This is easier than trying |
| 874 | // to crawl around a render tree with potential :before/:after content and |
| 875 | // anonymous blocks created by inline <body> tags etc. We can locate the <body> |
| 876 | // render object very easily via the DOM. |
| 877 | if (HTMLElement* body = document().bodyOrFrameset()) { |
| 878 | // Can't scroll a frameset document anyway. |
| 879 | isOpaqueRoot = is<HTMLFrameSetElement>(*body); |
| 880 | } else { |
| 881 | // SVG documents and XML documents with SVG root nodes are transparent. |
| 882 | isOpaqueRoot = !document().hasSVGRootNode(); |
| 883 | } |
| 884 | } |
| 885 | } else |
| 886 | isOpaqueRoot = !view().frameView().isTransparent(); |
| 887 | } |
| 888 | view().frameView().setContentIsOpaque(isOpaqueRoot); |
| 889 | } |
| 890 | |
| 891 | // Paint the color first underneath all images, culled if background image occludes it. |
| 892 | // FIXME: In the bgLayer.hasFiniteBounds() case, we could improve the culling test |
| 893 | // by verifying whether the background image covers the entire layout rect. |
| 894 | if (!bgLayer.next()) { |
| 895 | LayoutRect backgroundRect(scrolledPaintRect); |
| 896 | bool boxShadowShouldBeAppliedToBackground = this->boxShadowShouldBeAppliedToBackground(rect.location(), bleedAvoidance, box); |
| 897 | if (boxShadowShouldBeAppliedToBackground || !shouldPaintBackgroundImage || !bgLayer.hasOpaqueImage(*this) || !bgLayer.hasRepeatXY() || bgLayer.isEmpty()) { |
| 898 | if (!boxShadowShouldBeAppliedToBackground) |
| 899 | backgroundRect.intersect(paintInfo.rect); |
| 900 | |
| 901 | // If we have an alpha and we are painting the root element, blend with the base background color. |
| 902 | Color baseColor; |
| 903 | bool shouldClearBackground = false; |
| 904 | if ((baseBgColorUsage != BaseBackgroundColorSkip) && isOpaqueRoot) { |
| 905 | baseColor = view().frameView().baseBackgroundColor(); |
| 906 | if (!baseColor.isVisible()) |
| 907 | shouldClearBackground = true; |
| 908 | } |
| 909 | |
| 910 | GraphicsContextStateSaver shadowStateSaver(context, boxShadowShouldBeAppliedToBackground); |
| 911 | if (boxShadowShouldBeAppliedToBackground) |
| 912 | applyBoxShadowForBackground(context, style()); |
| 913 | |
| 914 | FloatRect backgroundRectForPainting = snapRectToDevicePixels(backgroundRect, deviceScaleFactor); |
| 915 | if (baseColor.isVisible()) { |
| 916 | if (!baseBgColorOnly && bgColor.isVisible()) |
| 917 | baseColor = baseColor.blend(bgColor); |
| 918 | context.fillRect(backgroundRectForPainting, baseColor, CompositeCopy); |
| 919 | } else if (!baseBgColorOnly && bgColor.isVisible()) { |
| 920 | auto operation = context.compositeOperation(); |
| 921 | if (shouldClearBackground) { |
| 922 | if (op == CompositeDestinationOut) // We're punching out the background. |
| 923 | operation = op; |
| 924 | else |
| 925 | operation = CompositeCopy; |
| 926 | } |
| 927 | context.fillRect(backgroundRectForPainting, bgColor, operation); |
| 928 | } else if (shouldClearBackground) |
| 929 | context.clearRect(backgroundRectForPainting); |
| 930 | } |
| 931 | } |
| 932 | |
| 933 | // no progressive loading of the background image |
| 934 | if (!baseBgColorOnly && shouldPaintBackgroundImage) { |
| 935 | auto geometry = calculateBackgroundImageGeometry(paintInfo.paintContainer, bgLayer, rect.location(), scrolledPaintRect, backgroundObject); |
| 936 | geometry.clip(LayoutRect(pixelSnappedRect)); |
| 937 | RefPtr<Image> image; |
| 938 | if (!geometry.destRect().isEmpty() && (image = bgImage->image(backgroundObject ? backgroundObject : this, geometry.tileSize()))) { |
| 939 | auto compositeOp = op == CompositeSourceOver ? bgLayer.composite() : op; |
| 940 | context.setDrawLuminanceMask(bgLayer.maskSourceType() == MaskSourceType::Luminance); |
| 941 | |
| 942 | if (is<BitmapImage>(image)) |
| 943 | downcast<BitmapImage>(*image).updateFromSettings(settings()); |
| 944 | |
| 945 | auto interpolation = chooseInterpolationQuality(context, *image, &bgLayer, geometry.tileSize()); |
| 946 | auto decodingMode = decodingModeForImageDraw(*image, paintInfo); |
| 947 | auto drawResult = context.drawTiledImage(*image, geometry.destRect(), toLayoutPoint(geometry.relativePhase()), geometry.tileSize(), geometry.spaceSize(), ImagePaintingOptions(compositeOp, bgLayer.blendMode(), decodingMode, ImageOrientationDescription(), interpolation)); |
| 948 | if (drawResult == ImageDrawResult::DidRequestDecoding) { |
| 949 | ASSERT(bgImage->isCachedImage()); |
| 950 | bgImage->cachedImage()->addClientWaitingForAsyncDecoding(*this); |
| 951 | } |
| 952 | } |
| 953 | } |
| 954 | |
| 955 | if (maskImage && bgLayer.clip() == FillBox::Text) { |
| 956 | context.drawConsumingImageBuffer(WTFMove(maskImage), maskRect, CompositeDestinationIn); |
| 957 | context.endTransparencyLayer(); |
| 958 | } |
| 959 | } |
| 960 | |
| 961 | static inline LayoutUnit resolveWidthForRatio(LayoutUnit height, const LayoutSize& intrinsicRatio) |
| 962 | { |
| 963 | return height * intrinsicRatio.width() / intrinsicRatio.height(); |
| 964 | } |
| 965 | |
| 966 | static inline LayoutUnit resolveHeightForRatio(LayoutUnit width, const LayoutSize& intrinsicRatio) |
| 967 | { |
| 968 | return width * intrinsicRatio.height() / intrinsicRatio.width(); |
| 969 | } |
| 970 | |
| 971 | static inline LayoutSize resolveAgainstIntrinsicWidthOrHeightAndRatio(const LayoutSize& size, const LayoutSize& intrinsicRatio, LayoutUnit useWidth, LayoutUnit useHeight) |
| 972 | { |
| 973 | if (intrinsicRatio.isEmpty()) { |
| 974 | if (useWidth) |
| 975 | return LayoutSize(useWidth, size.height()); |
| 976 | return LayoutSize(size.width(), useHeight); |
| 977 | } |
| 978 | |
| 979 | if (useWidth) |
| 980 | return LayoutSize(useWidth, resolveHeightForRatio(useWidth, intrinsicRatio)); |
| 981 | return LayoutSize(resolveWidthForRatio(useHeight, intrinsicRatio), useHeight); |
| 982 | } |
| 983 | |
| 984 | static inline LayoutSize resolveAgainstIntrinsicRatio(const LayoutSize& size, const LayoutSize& intrinsicRatio) |
| 985 | { |
| 986 | // Two possible solutions: (size.width(), solutionHeight) or (solutionWidth, size.height()) |
| 987 | // "... must be assumed to be the largest dimensions..." = easiest answer: the rect with the largest surface area. |
| 988 | |
| 989 | LayoutUnit solutionWidth = resolveWidthForRatio(size.height(), intrinsicRatio); |
| 990 | LayoutUnit solutionHeight = resolveHeightForRatio(size.width(), intrinsicRatio); |
| 991 | if (solutionWidth <= size.width()) { |
| 992 | if (solutionHeight <= size.height()) { |
| 993 | // If both solutions fit, choose the one covering the larger area. |
| 994 | LayoutUnit areaOne = solutionWidth * size.height(); |
| 995 | LayoutUnit areaTwo = size.width() * solutionHeight; |
| 996 | if (areaOne < areaTwo) |
| 997 | return LayoutSize(size.width(), solutionHeight); |
| 998 | return LayoutSize(solutionWidth, size.height()); |
| 999 | } |
| 1000 | |
| 1001 | // Only the first solution fits. |
| 1002 | return LayoutSize(solutionWidth, size.height()); |
| 1003 | } |
| 1004 | |
| 1005 | // Only the second solution fits, assert that. |
| 1006 | ASSERT(solutionHeight <= size.height()); |
| 1007 | return LayoutSize(size.width(), solutionHeight); |
| 1008 | } |
| 1009 | |
| 1010 | LayoutSize RenderBoxModelObject::calculateImageIntrinsicDimensions(StyleImage* image, const LayoutSize& positioningAreaSize, ScaleByEffectiveZoomOrNot shouldScaleOrNot) const |
| 1011 | { |
| 1012 | // A generated image without a fixed size, will always return the container size as intrinsic size. |
| 1013 | if (image->isGeneratedImage() && image->usesImageContainerSize()) |
| 1014 | return LayoutSize(positioningAreaSize.width(), positioningAreaSize.height()); |
| 1015 | |
| 1016 | Length intrinsicWidth; |
| 1017 | Length intrinsicHeight; |
| 1018 | FloatSize intrinsicRatio; |
| 1019 | image->computeIntrinsicDimensions(this, intrinsicWidth, intrinsicHeight, intrinsicRatio); |
| 1020 | |
| 1021 | ASSERT(!intrinsicWidth.isPercentOrCalculated()); |
| 1022 | ASSERT(!intrinsicHeight.isPercentOrCalculated()); |
| 1023 | |
| 1024 | LayoutSize resolvedSize(intrinsicWidth.value(), intrinsicHeight.value()); |
| 1025 | LayoutSize minimumSize(resolvedSize.width() > 0 ? 1 : 0, resolvedSize.height() > 0 ? 1 : 0); |
| 1026 | |
| 1027 | if (shouldScaleOrNot == ScaleByEffectiveZoom) |
| 1028 | resolvedSize.scale(style().effectiveZoom()); |
| 1029 | resolvedSize.clampToMinimumSize(minimumSize); |
| 1030 | |
| 1031 | if (!resolvedSize.isEmpty()) |
| 1032 | return resolvedSize; |
| 1033 | |
| 1034 | // If the image has one of either an intrinsic width or an intrinsic height: |
| 1035 | // * and an intrinsic aspect ratio, then the missing dimension is calculated from the given dimension and the ratio. |
| 1036 | // * and no intrinsic aspect ratio, then the missing dimension is assumed to be the size of the rectangle that |
| 1037 | // establishes the coordinate system for the 'background-position' property. |
| 1038 | if (resolvedSize.width() > 0 || resolvedSize.height() > 0) |
| 1039 | return resolveAgainstIntrinsicWidthOrHeightAndRatio(positioningAreaSize, LayoutSize(intrinsicRatio), resolvedSize.width(), resolvedSize.height()); |
| 1040 | |
| 1041 | // If the image has no intrinsic dimensions and has an intrinsic ratio the dimensions must be assumed to be the |
| 1042 | // largest dimensions at that ratio such that neither dimension exceeds the dimensions of the rectangle that |
| 1043 | // establishes the coordinate system for the 'background-position' property. |
| 1044 | if (!intrinsicRatio.isEmpty()) |
| 1045 | return resolveAgainstIntrinsicRatio(positioningAreaSize, LayoutSize(intrinsicRatio)); |
| 1046 | |
| 1047 | // If the image has no intrinsic ratio either, then the dimensions must be assumed to be the rectangle that |
| 1048 | // establishes the coordinate system for the 'background-position' property. |
| 1049 | return positioningAreaSize; |
| 1050 | } |
| 1051 | |
| 1052 | LayoutSize RenderBoxModelObject::calculateFillTileSize(const FillLayer& fillLayer, const LayoutSize& positioningAreaSize) const |
| 1053 | { |
| 1054 | StyleImage* image = fillLayer.image(); |
| 1055 | FillSizeType type = fillLayer.size().type; |
| 1056 | |
| 1057 | LayoutSize imageIntrinsicSize; |
| 1058 | if (image) { |
| 1059 | imageIntrinsicSize = calculateImageIntrinsicDimensions(image, positioningAreaSize, ScaleByEffectiveZoom); |
| 1060 | imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor()); |
| 1061 | } else |
| 1062 | imageIntrinsicSize = positioningAreaSize; |
| 1063 | |
| 1064 | switch (type) { |
| 1065 | case FillSizeType::Size: { |
| 1066 | LayoutSize tileSize = positioningAreaSize; |
| 1067 | |
| 1068 | Length layerWidth = fillLayer.size().size.width; |
| 1069 | Length layerHeight = fillLayer.size().size.height; |
| 1070 | |
| 1071 | if (layerWidth.isFixed()) |
| 1072 | tileSize.setWidth(layerWidth.value()); |
| 1073 | else if (layerWidth.isPercentOrCalculated()) |
| 1074 | tileSize.setWidth(valueForLength(layerWidth, positioningAreaSize.width())); |
| 1075 | |
| 1076 | if (layerHeight.isFixed()) |
| 1077 | tileSize.setHeight(layerHeight.value()); |
| 1078 | else if (layerHeight.isPercentOrCalculated()) |
| 1079 | tileSize.setHeight(valueForLength(layerHeight, positioningAreaSize.height())); |
| 1080 | |
| 1081 | // If one of the values is auto we have to use the appropriate |
| 1082 | // scale to maintain our aspect ratio. |
| 1083 | if (layerWidth.isAuto() && !layerHeight.isAuto()) { |
| 1084 | if (imageIntrinsicSize.height()) |
| 1085 | tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height()); |
| 1086 | } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { |
| 1087 | if (imageIntrinsicSize.width()) |
| 1088 | tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width()); |
| 1089 | } else if (layerWidth.isAuto() && layerHeight.isAuto()) { |
| 1090 | // If both width and height are auto, use the image's intrinsic size. |
| 1091 | tileSize = imageIntrinsicSize; |
| 1092 | } |
| 1093 | |
| 1094 | tileSize.clampNegativeToZero(); |
| 1095 | return tileSize; |
| 1096 | } |
| 1097 | case FillSizeType::None: { |
| 1098 | // If both values are ‘auto’ then the intrinsic width and/or height of the image should be used, if any. |
| 1099 | if (!imageIntrinsicSize.isEmpty()) |
| 1100 | return imageIntrinsicSize; |
| 1101 | |
| 1102 | // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for ‘contain’. |
| 1103 | type = FillSizeType::Contain; |
| 1104 | } |
| 1105 | FALLTHROUGH; |
| 1106 | case FillSizeType::Contain: |
| 1107 | case FillSizeType::Cover: { |
| 1108 | // Scale computation needs higher precision than what LayoutUnit can offer. |
| 1109 | FloatSize localImageIntrinsicSize = imageIntrinsicSize; |
| 1110 | FloatSize localPositioningAreaSize = positioningAreaSize; |
| 1111 | |
| 1112 | float horizontalScaleFactor = localImageIntrinsicSize.width() ? (localPositioningAreaSize.width() / localImageIntrinsicSize.width()) : 1; |
| 1113 | float verticalScaleFactor = localImageIntrinsicSize.height() ? (localPositioningAreaSize.height() / localImageIntrinsicSize.height()) : 1; |
| 1114 | float scaleFactor = type == FillSizeType::Contain ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor); |
| 1115 | float singleScaledPixel = 1.0 / document().deviceScaleFactor(); |
| 1116 | |
| 1117 | if (localImageIntrinsicSize.isEmpty()) |
| 1118 | return { }; |
| 1119 | |
| 1120 | return LayoutSize(localImageIntrinsicSize.scaled(scaleFactor).expandedTo({ singleScaledPixel, singleScaledPixel })); |
| 1121 | } |
| 1122 | } |
| 1123 | |
| 1124 | ASSERT_NOT_REACHED(); |
| 1125 | return { }; |
| 1126 | } |
| 1127 | |
| 1128 | static void pixelSnapBackgroundImageGeometryForPainting(LayoutRect& destinationRect, LayoutSize& tileSize, LayoutSize& phase, LayoutSize& space, float scaleFactor) |
| 1129 | { |
| 1130 | tileSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), tileSize), scaleFactor).size()); |
| 1131 | phase = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), phase), scaleFactor).size()); |
| 1132 | space = LayoutSize(snapRectToDevicePixels(LayoutRect(LayoutPoint(), space), scaleFactor).size()); |
| 1133 | destinationRect = LayoutRect(snapRectToDevicePixels(destinationRect, scaleFactor)); |
| 1134 | } |
| 1135 | |
| 1136 | bool RenderBoxModelObject::fixedBackgroundPaintsInLocalCoordinates() const |
| 1137 | { |
| 1138 | if (!isDocumentElementRenderer()) |
| 1139 | return false; |
| 1140 | |
| 1141 | if (view().frameView().paintBehavior().contains(PaintBehavior::FlattenCompositingLayers)) |
| 1142 | return false; |
| 1143 | |
| 1144 | RenderLayer* rootLayer = view().layer(); |
| 1145 | if (!rootLayer || !rootLayer->isComposited()) |
| 1146 | return false; |
| 1147 | |
| 1148 | return rootLayer->backing()->backgroundLayerPaintsFixedRootBackground(); |
| 1149 | } |
| 1150 | |
| 1151 | static inline LayoutUnit getSpace(LayoutUnit areaSize, LayoutUnit tileSize) |
| 1152 | { |
| 1153 | int numberOfTiles = areaSize / tileSize; |
| 1154 | LayoutUnit space = -1; |
| 1155 | |
| 1156 | if (numberOfTiles > 1) |
| 1157 | space = (areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1); |
| 1158 | |
| 1159 | return space; |
| 1160 | } |
| 1161 | |
| 1162 | static LayoutUnit resolveEdgeRelativeLength(const Length& length, Edge edge, LayoutUnit availableSpace, const LayoutSize& areaSize, const LayoutSize& tileSize) |
| 1163 | { |
| 1164 | LayoutUnit result = minimumValueForLength(length, availableSpace); |
| 1165 | |
| 1166 | if (edge == Edge::Right) |
| 1167 | return areaSize.width() - tileSize.width() - result; |
| 1168 | |
| 1169 | if (edge == Edge::Bottom) |
| 1170 | return areaSize.height() - tileSize.height() - result; |
| 1171 | |
| 1172 | return result; |
| 1173 | } |
| 1174 | |
| 1175 | BackgroundImageGeometry RenderBoxModelObject::calculateBackgroundImageGeometry(const RenderLayerModelObject* paintContainer, const FillLayer& fillLayer, const LayoutPoint& paintOffset, |
| 1176 | const LayoutRect& borderBoxRect, RenderElement* backgroundObject) const |
| 1177 | { |
| 1178 | LayoutUnit left; |
| 1179 | LayoutUnit top; |
| 1180 | LayoutSize positioningAreaSize; |
| 1181 | // Determine the background positioning area and set destination rect to the background painting area. |
| 1182 | // Destination rect will be adjusted later if the background is non-repeating. |
| 1183 | // FIXME: transforms spec says that fixed backgrounds behave like scroll inside transforms. https://bugs.webkit.org/show_bug.cgi?id=15679 |
| 1184 | LayoutRect destinationRect(borderBoxRect); |
| 1185 | bool fixedAttachment = fillLayer.attachment() == FillAttachment::FixedBackground; |
| 1186 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 1187 | if (!fixedAttachment) { |
| 1188 | LayoutUnit right; |
| 1189 | LayoutUnit bottom; |
| 1190 | // Scroll and Local. |
| 1191 | if (fillLayer.origin() != FillBox::Border) { |
| 1192 | left = borderLeft(); |
| 1193 | right = borderRight(); |
| 1194 | top = borderTop(); |
| 1195 | bottom = borderBottom(); |
| 1196 | if (fillLayer.origin() == FillBox::Content) { |
| 1197 | left += paddingLeft(); |
| 1198 | right += paddingRight(); |
| 1199 | top += paddingTop(); |
| 1200 | bottom += paddingBottom(); |
| 1201 | } |
| 1202 | } |
| 1203 | |
| 1204 | // The background of the box generated by the root element covers the entire canvas including |
| 1205 | // its margins. Since those were added in already, we have to factor them out when computing |
| 1206 | // the background positioning area. |
| 1207 | if (isDocumentElementRenderer()) { |
| 1208 | positioningAreaSize = downcast<RenderBox>(*this).size() - LayoutSize(left + right, top + bottom); |
| 1209 | positioningAreaSize = LayoutSize(snapSizeToDevicePixel(positioningAreaSize, LayoutPoint(), deviceScaleFactor)); |
| 1210 | if (view().frameView().hasExtendedBackgroundRectForPainting()) { |
| 1211 | LayoutRect extendedBackgroundRect = view().frameView().extendedBackgroundRectForPainting(); |
| 1212 | left += (marginLeft() - extendedBackgroundRect.x()); |
| 1213 | top += (marginTop() - extendedBackgroundRect.y()); |
| 1214 | } |
| 1215 | } else { |
| 1216 | positioningAreaSize = borderBoxRect.size() - LayoutSize(left + right, top + bottom); |
| 1217 | positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(paintOffset, positioningAreaSize), deviceScaleFactor).size()); |
| 1218 | } |
| 1219 | } else { |
| 1220 | LayoutRect viewportRect; |
| 1221 | float topContentInset = 0; |
| 1222 | if (settings().fixedBackgroundsPaintRelativeToDocument()) |
| 1223 | viewportRect = view().unscaledDocumentRect(); |
| 1224 | else { |
| 1225 | FrameView& frameView = view().frameView(); |
| 1226 | bool useFixedLayout = frameView.useFixedLayout() && !frameView.fixedLayoutSize().isEmpty(); |
| 1227 | |
| 1228 | if (useFixedLayout) { |
| 1229 | // Use the fixedLayoutSize() when useFixedLayout() because the rendering will scale |
| 1230 | // down the frameView to to fit in the current viewport. |
| 1231 | viewportRect.setSize(frameView.fixedLayoutSize()); |
| 1232 | } else |
| 1233 | viewportRect.setSize(frameView.sizeForVisibleContent()); |
| 1234 | |
| 1235 | if (fixedBackgroundPaintsInLocalCoordinates()) { |
| 1236 | if (!useFixedLayout) { |
| 1237 | // Shifting location up by topContentInset is needed for layout tests which expect |
| 1238 | // layout to be shifted down when calling window.internals.setTopContentInset(). |
| 1239 | topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); |
| 1240 | viewportRect.setLocation(LayoutPoint(0, -topContentInset)); |
| 1241 | } |
| 1242 | } else if (useFixedLayout || frameView.frameScaleFactor() != 1) { |
| 1243 | // scrollPositionForFixedPosition() is adjusted for page scale and it does not include |
| 1244 | // topContentInset so do not add it to the calculation below. |
| 1245 | viewportRect.setLocation(frameView.scrollPositionForFixedPosition()); |
| 1246 | } else { |
| 1247 | // documentScrollPositionRelativeToViewOrigin() includes -topContentInset in its height |
| 1248 | // so we need to account for that in calculating the phase size |
| 1249 | topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); |
| 1250 | viewportRect.setLocation(frameView.documentScrollPositionRelativeToViewOrigin()); |
| 1251 | } |
| 1252 | |
| 1253 | top += topContentInset; |
| 1254 | } |
| 1255 | |
| 1256 | if (paintContainer) |
| 1257 | viewportRect.moveBy(LayoutPoint(-paintContainer->localToAbsolute(FloatPoint()))); |
| 1258 | |
| 1259 | destinationRect = viewportRect; |
| 1260 | positioningAreaSize = destinationRect.size(); |
| 1261 | positioningAreaSize.setHeight(positioningAreaSize.height() - topContentInset); |
| 1262 | positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), positioningAreaSize), deviceScaleFactor).size()); |
| 1263 | } |
| 1264 | |
| 1265 | auto clientForBackgroundImage = backgroundObject ? backgroundObject : this; |
| 1266 | LayoutSize tileSize = calculateFillTileSize(fillLayer, positioningAreaSize); |
| 1267 | if (StyleImage* layerImage = fillLayer.image()) |
| 1268 | layerImage->setContainerContextForRenderer(*clientForBackgroundImage, tileSize, style().effectiveZoom()); |
| 1269 | |
| 1270 | FillRepeat backgroundRepeatX = fillLayer.repeatX(); |
| 1271 | FillRepeat backgroundRepeatY = fillLayer.repeatY(); |
| 1272 | LayoutUnit availableWidth = positioningAreaSize.width() - tileSize.width(); |
| 1273 | LayoutUnit availableHeight = positioningAreaSize.height() - tileSize.height(); |
| 1274 | |
| 1275 | LayoutSize spaceSize; |
| 1276 | LayoutSize phase; |
| 1277 | LayoutSize noRepeat; |
| 1278 | LayoutUnit computedXPosition = resolveEdgeRelativeLength(fillLayer.xPosition(), fillLayer.backgroundXOrigin(), availableWidth, positioningAreaSize, tileSize); |
| 1279 | if (backgroundRepeatX == FillRepeat::Round && positioningAreaSize.width() > 0 && tileSize.width() > 0) { |
| 1280 | int numTiles = std::max(1, roundToInt(positioningAreaSize.width() / tileSize.width())); |
| 1281 | if (fillLayer.size().size.height.isAuto() && backgroundRepeatY != FillRepeat::Round) |
| 1282 | tileSize.setHeight(tileSize.height() * positioningAreaSize.width() / (numTiles * tileSize.width())); |
| 1283 | |
| 1284 | tileSize.setWidth(positioningAreaSize.width() / numTiles); |
| 1285 | phase.setWidth(tileSize.width() ? tileSize.width() - fmodf((computedXPosition + left), tileSize.width()) : 0); |
| 1286 | } |
| 1287 | |
| 1288 | LayoutUnit computedYPosition = resolveEdgeRelativeLength(fillLayer.yPosition(), fillLayer.backgroundYOrigin(), availableHeight, positioningAreaSize, tileSize); |
| 1289 | if (backgroundRepeatY == FillRepeat::Round && positioningAreaSize.height() > 0 && tileSize.height() > 0) { |
| 1290 | int numTiles = std::max(1, roundToInt(positioningAreaSize.height() / tileSize.height())); |
| 1291 | if (fillLayer.size().size.width.isAuto() && backgroundRepeatX != FillRepeat::Round) |
| 1292 | tileSize.setWidth(tileSize.width() * positioningAreaSize.height() / (numTiles * tileSize.height())); |
| 1293 | |
| 1294 | tileSize.setHeight(positioningAreaSize.height() / numTiles); |
| 1295 | phase.setHeight(tileSize.height() ? tileSize.height() - fmodf((computedYPosition + top), tileSize.height()) : 0); |
| 1296 | } |
| 1297 | |
| 1298 | if (backgroundRepeatX == FillRepeat::Repeat) { |
| 1299 | phase.setWidth(tileSize.width() ? tileSize.width() - fmodf(computedXPosition + left, tileSize.width()) : 0); |
| 1300 | spaceSize.setWidth(0); |
| 1301 | } else if (backgroundRepeatX == FillRepeat::Space && tileSize.width() > 0) { |
| 1302 | LayoutUnit space = getSpace(positioningAreaSize.width(), tileSize.width()); |
| 1303 | if (space >= 0) { |
| 1304 | LayoutUnit actualWidth = tileSize.width() + space; |
| 1305 | computedXPosition = minimumValueForLength(Length(), availableWidth); |
| 1306 | spaceSize.setWidth(space); |
| 1307 | spaceSize.setHeight(0); |
| 1308 | phase.setWidth(actualWidth ? actualWidth - fmodf((computedXPosition + left), actualWidth) : 0); |
| 1309 | } else |
| 1310 | backgroundRepeatX = FillRepeat::NoRepeat; |
| 1311 | } |
| 1312 | |
| 1313 | if (backgroundRepeatX == FillRepeat::NoRepeat) { |
| 1314 | LayoutUnit xOffset = left + computedXPosition; |
| 1315 | if (xOffset > 0) |
| 1316 | destinationRect.move(xOffset, 0_lu); |
| 1317 | xOffset = std::min<LayoutUnit>(xOffset, 0); |
| 1318 | phase.setWidth(-xOffset); |
| 1319 | destinationRect.setWidth(tileSize.width() + xOffset); |
| 1320 | spaceSize.setWidth(0); |
| 1321 | } |
| 1322 | |
| 1323 | if (backgroundRepeatY == FillRepeat::Repeat) { |
| 1324 | phase.setHeight(tileSize.height() ? tileSize.height() - fmodf(computedYPosition + top, tileSize.height()) : 0); |
| 1325 | spaceSize.setHeight(0); |
| 1326 | } else if (backgroundRepeatY == FillRepeat::Space && tileSize.height() > 0) { |
| 1327 | LayoutUnit space = getSpace(positioningAreaSize.height(), tileSize.height()); |
| 1328 | |
| 1329 | if (space >= 0) { |
| 1330 | LayoutUnit actualHeight = tileSize.height() + space; |
| 1331 | computedYPosition = minimumValueForLength(Length(), availableHeight); |
| 1332 | spaceSize.setHeight(space); |
| 1333 | phase.setHeight(actualHeight ? actualHeight - fmodf((computedYPosition + top), actualHeight) : 0); |
| 1334 | } else |
| 1335 | backgroundRepeatY = FillRepeat::NoRepeat; |
| 1336 | } |
| 1337 | if (backgroundRepeatY == FillRepeat::NoRepeat) { |
| 1338 | LayoutUnit yOffset = top + computedYPosition; |
| 1339 | if (yOffset > 0) |
| 1340 | destinationRect.move(0_lu, yOffset); |
| 1341 | yOffset = std::min<LayoutUnit>(yOffset, 0); |
| 1342 | phase.setHeight(-yOffset); |
| 1343 | destinationRect.setHeight(tileSize.height() + yOffset); |
| 1344 | spaceSize.setHeight(0); |
| 1345 | } |
| 1346 | |
| 1347 | if (fixedAttachment) { |
| 1348 | LayoutPoint attachmentPoint = borderBoxRect.location(); |
| 1349 | phase.expand(std::max<LayoutUnit>(attachmentPoint.x() - destinationRect.x(), 0), std::max<LayoutUnit>(attachmentPoint.y() - destinationRect.y(), 0)); |
| 1350 | } |
| 1351 | |
| 1352 | destinationRect.intersect(borderBoxRect); |
| 1353 | pixelSnapBackgroundImageGeometryForPainting(destinationRect, tileSize, phase, spaceSize, deviceScaleFactor); |
| 1354 | return BackgroundImageGeometry(destinationRect, tileSize, phase, spaceSize, fixedAttachment); |
| 1355 | } |
| 1356 | |
| 1357 | void RenderBoxModelObject::getGeometryForBackgroundImage(const RenderLayerModelObject* paintContainer, const LayoutPoint& paintOffset, FloatRect& destRect, FloatSize& phase, FloatSize& tileSize) const |
| 1358 | { |
| 1359 | LayoutRect paintRect(destRect); |
| 1360 | auto geometry = calculateBackgroundImageGeometry(paintContainer, style().backgroundLayers(), paintOffset, paintRect); |
| 1361 | phase = geometry.phase(); |
| 1362 | tileSize = geometry.tileSize(); |
| 1363 | destRect = geometry.destRect(); |
| 1364 | } |
| 1365 | |
| 1366 | bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext& graphicsContext, const LayoutRect& rect, const RenderStyle& style, |
| 1367 | const NinePieceImage& ninePieceImage, CompositeOperator op) |
| 1368 | { |
| 1369 | StyleImage* styleImage = ninePieceImage.image(); |
| 1370 | if (!styleImage) |
| 1371 | return false; |
| 1372 | |
| 1373 | if (!styleImage->isLoaded()) |
| 1374 | return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either. |
| 1375 | |
| 1376 | if (!styleImage->canRender(this, style.effectiveZoom())) |
| 1377 | return false; |
| 1378 | |
| 1379 | // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function |
| 1380 | // doesn't have any understanding of the zoom that is in effect on the tile. |
| 1381 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 1382 | |
| 1383 | LayoutRect rectWithOutsets = rect; |
| 1384 | rectWithOutsets.expand(style.imageOutsets(ninePieceImage)); |
| 1385 | LayoutRect destination = LayoutRect(snapRectToDevicePixels(rectWithOutsets, deviceScaleFactor)); |
| 1386 | |
| 1387 | LayoutSize source = calculateImageIntrinsicDimensions(styleImage, destination.size(), DoNotScaleByEffectiveZoom); |
| 1388 | |
| 1389 | // If both values are ‘auto’ then the intrinsic width and/or height of the image should be used, if any. |
| 1390 | styleImage->setContainerContextForRenderer(*this, source, style.effectiveZoom()); |
| 1391 | |
| 1392 | ninePieceImage.paint(graphicsContext, this, style, destination, source, deviceScaleFactor, op); |
| 1393 | return true; |
| 1394 | } |
| 1395 | |
| 1396 | static bool allCornersClippedOut(const RoundedRect& border, const LayoutRect& clipRect) |
| 1397 | { |
| 1398 | LayoutRect boundingRect = border.rect(); |
| 1399 | if (clipRect.contains(boundingRect)) |
| 1400 | return false; |
| 1401 | |
| 1402 | RoundedRect::Radii radii = border.radii(); |
| 1403 | |
| 1404 | LayoutRect topLeftRect(boundingRect.location(), radii.topLeft()); |
| 1405 | if (clipRect.intersects(topLeftRect)) |
| 1406 | return false; |
| 1407 | |
| 1408 | LayoutRect topRightRect(boundingRect.location(), radii.topRight()); |
| 1409 | topRightRect.setX(boundingRect.maxX() - topRightRect.width()); |
| 1410 | if (clipRect.intersects(topRightRect)) |
| 1411 | return false; |
| 1412 | |
| 1413 | LayoutRect bottomLeftRect(boundingRect.location(), radii.bottomLeft()); |
| 1414 | bottomLeftRect.setY(boundingRect.maxY() - bottomLeftRect.height()); |
| 1415 | if (clipRect.intersects(bottomLeftRect)) |
| 1416 | return false; |
| 1417 | |
| 1418 | LayoutRect bottomRightRect(boundingRect.location(), radii.bottomRight()); |
| 1419 | bottomRightRect.setX(boundingRect.maxX() - bottomRightRect.width()); |
| 1420 | bottomRightRect.setY(boundingRect.maxY() - bottomRightRect.height()); |
| 1421 | if (clipRect.intersects(bottomRightRect)) |
| 1422 | return false; |
| 1423 | |
| 1424 | return true; |
| 1425 | } |
| 1426 | |
| 1427 | static bool borderWillArcInnerEdge(const LayoutSize& firstRadius, const FloatSize& secondRadius) |
| 1428 | { |
| 1429 | return !firstRadius.isZero() || !secondRadius.isZero(); |
| 1430 | } |
| 1431 | |
| 1432 | inline bool styleRequiresClipPolygon(BorderStyle style) |
| 1433 | { |
| 1434 | return style == BorderStyle::Dotted || style == BorderStyle::Dashed; // These are drawn with a stroke, so we have to clip to get corner miters. |
| 1435 | } |
| 1436 | |
| 1437 | static bool borderStyleFillsBorderArea(BorderStyle style) |
| 1438 | { |
| 1439 | return !(style == BorderStyle::Dotted || style == BorderStyle::Dashed || style == BorderStyle::Double); |
| 1440 | } |
| 1441 | |
| 1442 | static bool borderStyleHasInnerDetail(BorderStyle style) |
| 1443 | { |
| 1444 | return style == BorderStyle::Groove || style == BorderStyle::Ridge || style == BorderStyle::Double; |
| 1445 | } |
| 1446 | |
| 1447 | static bool borderStyleIsDottedOrDashed(BorderStyle style) |
| 1448 | { |
| 1449 | return style == BorderStyle::Dotted || style == BorderStyle::Dashed; |
| 1450 | } |
| 1451 | |
| 1452 | // BorderStyle::Outset darkens the bottom and right (and maybe lightens the top and left) |
| 1453 | // BorderStyle::Inset darkens the top and left (and maybe lightens the bottom and right) |
| 1454 | static inline bool borderStyleHasUnmatchedColorsAtCorner(BorderStyle style, BoxSide side, BoxSide adjacentSide) |
| 1455 | { |
| 1456 | // These styles match at the top/left and bottom/right. |
| 1457 | if (style == BorderStyle::Inset || style == BorderStyle::Groove || style == BorderStyle::Ridge || style == BorderStyle::Outset) { |
| 1458 | const BorderEdgeFlags topRightFlags = edgeFlagForSide(BSTop) | edgeFlagForSide(BSRight); |
| 1459 | const BorderEdgeFlags bottomLeftFlags = edgeFlagForSide(BSBottom) | edgeFlagForSide(BSLeft); |
| 1460 | |
| 1461 | BorderEdgeFlags flags = edgeFlagForSide(side) | edgeFlagForSide(adjacentSide); |
| 1462 | return flags == topRightFlags || flags == bottomLeftFlags; |
| 1463 | } |
| 1464 | return false; |
| 1465 | } |
| 1466 | |
| 1467 | static inline bool colorsMatchAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) |
| 1468 | { |
| 1469 | if (edges[side].shouldRender() != edges[adjacentSide].shouldRender()) |
| 1470 | return false; |
| 1471 | |
| 1472 | if (!edgesShareColor(edges[side], edges[adjacentSide])) |
| 1473 | return false; |
| 1474 | |
| 1475 | return !borderStyleHasUnmatchedColorsAtCorner(edges[side].style(), side, adjacentSide); |
| 1476 | } |
| 1477 | |
| 1478 | |
| 1479 | static inline bool colorNeedsAntiAliasAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) |
| 1480 | { |
| 1481 | if (edges[side].color().isOpaque()) |
| 1482 | return false; |
| 1483 | |
| 1484 | if (edges[side].shouldRender() != edges[adjacentSide].shouldRender()) |
| 1485 | return false; |
| 1486 | |
| 1487 | if (!edgesShareColor(edges[side], edges[adjacentSide])) |
| 1488 | return true; |
| 1489 | |
| 1490 | return borderStyleHasUnmatchedColorsAtCorner(edges[side].style(), side, adjacentSide); |
| 1491 | } |
| 1492 | |
| 1493 | // This assumes that we draw in order: top, bottom, left, right. |
| 1494 | static inline bool willBeOverdrawn(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[]) |
| 1495 | { |
| 1496 | switch (side) { |
| 1497 | case BSTop: |
| 1498 | case BSBottom: |
| 1499 | if (edges[adjacentSide].presentButInvisible()) |
| 1500 | return false; |
| 1501 | |
| 1502 | if (!edgesShareColor(edges[side], edges[adjacentSide]) && !edges[adjacentSide].color().isOpaque()) |
| 1503 | return false; |
| 1504 | |
| 1505 | if (!borderStyleFillsBorderArea(edges[adjacentSide].style())) |
| 1506 | return false; |
| 1507 | |
| 1508 | return true; |
| 1509 | |
| 1510 | case BSLeft: |
| 1511 | case BSRight: |
| 1512 | // These draw last, so are never overdrawn. |
| 1513 | return false; |
| 1514 | } |
| 1515 | return false; |
| 1516 | } |
| 1517 | |
| 1518 | static inline bool borderStylesRequireMitre(BoxSide side, BoxSide adjacentSide, BorderStyle style, BorderStyle adjacentStyle) |
| 1519 | { |
| 1520 | if (style == BorderStyle::Double || adjacentStyle == BorderStyle::Double || adjacentStyle == BorderStyle::Groove || adjacentStyle == BorderStyle::Ridge) |
| 1521 | return true; |
| 1522 | |
| 1523 | if (borderStyleIsDottedOrDashed(style) != borderStyleIsDottedOrDashed(adjacentStyle)) |
| 1524 | return true; |
| 1525 | |
| 1526 | if (style != adjacentStyle) |
| 1527 | return true; |
| 1528 | |
| 1529 | return borderStyleHasUnmatchedColorsAtCorner(style, side, adjacentSide); |
| 1530 | } |
| 1531 | |
| 1532 | static bool joinRequiresMitre(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[], bool allowOverdraw) |
| 1533 | { |
| 1534 | if ((edges[side].isTransparent() && edges[adjacentSide].isTransparent()) || !edges[adjacentSide].isPresent()) |
| 1535 | return false; |
| 1536 | |
| 1537 | if (allowOverdraw && willBeOverdrawn(side, adjacentSide, edges)) |
| 1538 | return false; |
| 1539 | |
| 1540 | if (!edgesShareColor(edges[side], edges[adjacentSide])) |
| 1541 | return true; |
| 1542 | |
| 1543 | if (borderStylesRequireMitre(side, adjacentSide, edges[side].style(), edges[adjacentSide].style())) |
| 1544 | return true; |
| 1545 | |
| 1546 | return false; |
| 1547 | } |
| 1548 | |
| 1549 | static RoundedRect calculateAdjustedInnerBorder(const RoundedRect&innerBorder, BoxSide side) |
| 1550 | { |
| 1551 | // Expand the inner border as necessary to make it a rounded rect (i.e. radii contained within each edge). |
| 1552 | // This function relies on the fact we only get radii not contained within each edge if one of the radii |
| 1553 | // for an edge is zero, so we can shift the arc towards the zero radius corner. |
| 1554 | RoundedRect::Radii newRadii = innerBorder.radii(); |
| 1555 | LayoutRect newRect = innerBorder.rect(); |
| 1556 | |
| 1557 | float overshoot; |
| 1558 | float maxRadii; |
| 1559 | |
| 1560 | switch (side) { |
| 1561 | case BSTop: |
| 1562 | overshoot = newRadii.topLeft().width() + newRadii.topRight().width() - newRect.width(); |
| 1563 | if (overshoot > 0) { |
| 1564 | ASSERT(!(newRadii.topLeft().width() && newRadii.topRight().width())); |
| 1565 | newRect.setWidth(newRect.width() + overshoot); |
| 1566 | if (!newRadii.topLeft().width()) |
| 1567 | newRect.move(-overshoot, 0); |
| 1568 | } |
| 1569 | newRadii.setBottomLeft(IntSize(0, 0)); |
| 1570 | newRadii.setBottomRight(IntSize(0, 0)); |
| 1571 | maxRadii = std::max(newRadii.topLeft().height(), newRadii.topRight().height()); |
| 1572 | if (maxRadii > newRect.height()) |
| 1573 | newRect.setHeight(maxRadii); |
| 1574 | break; |
| 1575 | |
| 1576 | case BSBottom: |
| 1577 | overshoot = newRadii.bottomLeft().width() + newRadii.bottomRight().width() - newRect.width(); |
| 1578 | if (overshoot > 0) { |
| 1579 | ASSERT(!(newRadii.bottomLeft().width() && newRadii.bottomRight().width())); |
| 1580 | newRect.setWidth(newRect.width() + overshoot); |
| 1581 | if (!newRadii.bottomLeft().width()) |
| 1582 | newRect.move(-overshoot, 0); |
| 1583 | } |
| 1584 | newRadii.setTopLeft(IntSize(0, 0)); |
| 1585 | newRadii.setTopRight(IntSize(0, 0)); |
| 1586 | maxRadii = std::max(newRadii.bottomLeft().height(), newRadii.bottomRight().height()); |
| 1587 | if (maxRadii > newRect.height()) { |
| 1588 | newRect.move(0, newRect.height() - maxRadii); |
| 1589 | newRect.setHeight(maxRadii); |
| 1590 | } |
| 1591 | break; |
| 1592 | |
| 1593 | case BSLeft: |
| 1594 | overshoot = newRadii.topLeft().height() + newRadii.bottomLeft().height() - newRect.height(); |
| 1595 | if (overshoot > 0) { |
| 1596 | ASSERT(!(newRadii.topLeft().height() && newRadii.bottomLeft().height())); |
| 1597 | newRect.setHeight(newRect.height() + overshoot); |
| 1598 | if (!newRadii.topLeft().height()) |
| 1599 | newRect.move(0, -overshoot); |
| 1600 | } |
| 1601 | newRadii.setTopRight(IntSize(0, 0)); |
| 1602 | newRadii.setBottomRight(IntSize(0, 0)); |
| 1603 | maxRadii = std::max(newRadii.topLeft().width(), newRadii.bottomLeft().width()); |
| 1604 | if (maxRadii > newRect.width()) |
| 1605 | newRect.setWidth(maxRadii); |
| 1606 | break; |
| 1607 | |
| 1608 | case BSRight: |
| 1609 | overshoot = newRadii.topRight().height() + newRadii.bottomRight().height() - newRect.height(); |
| 1610 | if (overshoot > 0) { |
| 1611 | ASSERT(!(newRadii.topRight().height() && newRadii.bottomRight().height())); |
| 1612 | newRect.setHeight(newRect.height() + overshoot); |
| 1613 | if (!newRadii.topRight().height()) |
| 1614 | newRect.move(0, -overshoot); |
| 1615 | } |
| 1616 | newRadii.setTopLeft(IntSize(0, 0)); |
| 1617 | newRadii.setBottomLeft(IntSize(0, 0)); |
| 1618 | maxRadii = std::max(newRadii.topRight().width(), newRadii.bottomRight().width()); |
| 1619 | if (maxRadii > newRect.width()) { |
| 1620 | newRect.move(newRect.width() - maxRadii, 0); |
| 1621 | newRect.setWidth(maxRadii); |
| 1622 | } |
| 1623 | break; |
| 1624 | } |
| 1625 | |
| 1626 | return RoundedRect(newRect, newRadii); |
| 1627 | } |
| 1628 | |
| 1629 | void RenderBoxModelObject::paintOneBorderSide(GraphicsContext& graphicsContext, const RenderStyle& style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, |
| 1630 | const LayoutRect& sideRect, BoxSide side, BoxSide adjacentSide1, BoxSide adjacentSide2, const BorderEdge edges[], const Path* path, |
| 1631 | BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor) |
| 1632 | { |
| 1633 | const BorderEdge& edgeToRender = edges[side]; |
| 1634 | ASSERT(edgeToRender.widthForPainting()); |
| 1635 | const BorderEdge& adjacentEdge1 = edges[adjacentSide1]; |
| 1636 | const BorderEdge& adjacentEdge2 = edges[adjacentSide2]; |
| 1637 | |
| 1638 | bool mitreAdjacentSide1 = joinRequiresMitre(side, adjacentSide1, edges, !antialias); |
| 1639 | bool mitreAdjacentSide2 = joinRequiresMitre(side, adjacentSide2, edges, !antialias); |
| 1640 | |
| 1641 | bool adjacentSide1StylesMatch = colorsMatchAtCorner(side, adjacentSide1, edges); |
| 1642 | bool adjacentSide2StylesMatch = colorsMatchAtCorner(side, adjacentSide2, edges); |
| 1643 | |
| 1644 | const Color& colorToPaint = overrideColor ? *overrideColor : edgeToRender.color(); |
| 1645 | |
| 1646 | if (path) { |
| 1647 | GraphicsContextStateSaver stateSaver(graphicsContext); |
| 1648 | |
| 1649 | clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, adjacentSide1StylesMatch, adjacentSide2StylesMatch); |
| 1650 | |
| 1651 | if (!innerBorder.isRenderable()) |
| 1652 | graphicsContext.clipOutRoundedRect(FloatRoundedRect(calculateAdjustedInnerBorder(innerBorder, side))); |
| 1653 | |
| 1654 | float thickness = std::max(std::max(edgeToRender.widthForPainting(), adjacentEdge1.widthForPainting()), adjacentEdge2.widthForPainting()); |
| 1655 | drawBoxSideFromPath(graphicsContext, outerBorder.rect(), *path, edges, edgeToRender.widthForPainting(), thickness, side, style, |
| 1656 | colorToPaint, edgeToRender.style(), bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 1657 | } else { |
| 1658 | bool clipForStyle = styleRequiresClipPolygon(edgeToRender.style()) && (mitreAdjacentSide1 || mitreAdjacentSide2); |
| 1659 | bool clipAdjacentSide1 = colorNeedsAntiAliasAtCorner(side, adjacentSide1, edges) && mitreAdjacentSide1; |
| 1660 | bool clipAdjacentSide2 = colorNeedsAntiAliasAtCorner(side, adjacentSide2, edges) && mitreAdjacentSide2; |
| 1661 | bool shouldClip = clipForStyle || clipAdjacentSide1 || clipAdjacentSide2; |
| 1662 | |
| 1663 | GraphicsContextStateSaver clipStateSaver(graphicsContext, shouldClip); |
| 1664 | if (shouldClip) { |
| 1665 | bool aliasAdjacentSide1 = clipAdjacentSide1 || (clipForStyle && mitreAdjacentSide1); |
| 1666 | bool aliasAdjacentSide2 = clipAdjacentSide2 || (clipForStyle && mitreAdjacentSide2); |
| 1667 | clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, !aliasAdjacentSide1, !aliasAdjacentSide2); |
| 1668 | // Since we clipped, no need to draw with a mitre. |
| 1669 | mitreAdjacentSide1 = false; |
| 1670 | mitreAdjacentSide2 = false; |
| 1671 | } |
| 1672 | drawLineForBoxSide(graphicsContext, sideRect, side, colorToPaint, edgeToRender.style(), mitreAdjacentSide1 ? adjacentEdge1.widthForPainting() : 0, mitreAdjacentSide2 ? adjacentEdge2.widthForPainting() : 0, antialias); |
| 1673 | } |
| 1674 | } |
| 1675 | |
| 1676 | static LayoutRect calculateSideRect(const RoundedRect& outerBorder, const BorderEdge edges[], int side) |
| 1677 | { |
| 1678 | LayoutRect sideRect = outerBorder.rect(); |
| 1679 | float width = edges[side].widthForPainting(); |
| 1680 | |
| 1681 | if (side == BSTop) |
| 1682 | sideRect.setHeight(width); |
| 1683 | else if (side == BSBottom) |
| 1684 | sideRect.shiftYEdgeTo(sideRect.maxY() - width); |
| 1685 | else if (side == BSLeft) |
| 1686 | sideRect.setWidth(width); |
| 1687 | else |
| 1688 | sideRect.shiftXEdgeTo(sideRect.maxX() - width); |
| 1689 | |
| 1690 | return sideRect; |
| 1691 | } |
| 1692 | |
| 1693 | void RenderBoxModelObject::paintBorderSides(GraphicsContext& graphicsContext, const RenderStyle& style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, |
| 1694 | const IntPoint& innerBorderAdjustment, const BorderEdge edges[], BorderEdgeFlags edgeSet, BackgroundBleedAvoidance bleedAvoidance, |
| 1695 | bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor) |
| 1696 | { |
| 1697 | bool renderRadii = outerBorder.isRounded(); |
| 1698 | |
| 1699 | Path roundedPath; |
| 1700 | if (renderRadii) |
| 1701 | roundedPath.addRoundedRect(outerBorder); |
| 1702 | |
| 1703 | // The inner border adjustment for bleed avoidance mode BackgroundBleedBackgroundOverBorder |
| 1704 | // is only applied to sideRect, which is okay since BackgroundBleedBackgroundOverBorder |
| 1705 | // is only to be used for solid borders and the shape of the border painted by drawBoxSideFromPath |
| 1706 | // only depends on sideRect when painting solid borders. |
| 1707 | |
| 1708 | if (edges[BSTop].shouldRender() && includesEdge(edgeSet, BSTop)) { |
| 1709 | LayoutRect sideRect = outerBorder.rect(); |
| 1710 | sideRect.setHeight(edges[BSTop].widthForPainting() + innerBorderAdjustment.y()); |
| 1711 | |
| 1712 | bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSTop].style()) || borderWillArcInnerEdge(innerBorder.radii().topLeft(), innerBorder.radii().topRight())); |
| 1713 | paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSTop, BSLeft, BSRight, edges, usePath ? &roundedPath : nullptr, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); |
| 1714 | } |
| 1715 | |
| 1716 | if (edges[BSBottom].shouldRender() && includesEdge(edgeSet, BSBottom)) { |
| 1717 | LayoutRect sideRect = outerBorder.rect(); |
| 1718 | sideRect.shiftYEdgeTo(sideRect.maxY() - edges[BSBottom].widthForPainting() - innerBorderAdjustment.y()); |
| 1719 | |
| 1720 | bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSBottom].style()) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().bottomRight())); |
| 1721 | paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSBottom, BSLeft, BSRight, edges, usePath ? &roundedPath : nullptr, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); |
| 1722 | } |
| 1723 | |
| 1724 | if (edges[BSLeft].shouldRender() && includesEdge(edgeSet, BSLeft)) { |
| 1725 | LayoutRect sideRect = outerBorder.rect(); |
| 1726 | sideRect.setWidth(edges[BSLeft].widthForPainting() + innerBorderAdjustment.x()); |
| 1727 | |
| 1728 | bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSLeft].style()) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().topLeft())); |
| 1729 | paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSLeft, BSTop, BSBottom, edges, usePath ? &roundedPath : nullptr, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); |
| 1730 | } |
| 1731 | |
| 1732 | if (edges[BSRight].shouldRender() && includesEdge(edgeSet, BSRight)) { |
| 1733 | LayoutRect sideRect = outerBorder.rect(); |
| 1734 | sideRect.shiftXEdgeTo(sideRect.maxX() - edges[BSRight].widthForPainting() - innerBorderAdjustment.x()); |
| 1735 | |
| 1736 | bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSRight].style()) || borderWillArcInnerEdge(innerBorder.radii().bottomRight(), innerBorder.radii().topRight())); |
| 1737 | paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSRight, BSTop, BSBottom, edges, usePath ? &roundedPath : nullptr, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor); |
| 1738 | } |
| 1739 | } |
| 1740 | |
| 1741 | void RenderBoxModelObject::paintTranslucentBorderSides(GraphicsContext& graphicsContext, const RenderStyle& style, const RoundedRect& outerBorder, const RoundedRect& innerBorder, const IntPoint& innerBorderAdjustment, |
| 1742 | const BorderEdge edges[], BorderEdgeFlags edgesToDraw, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias) |
| 1743 | { |
| 1744 | // willBeOverdrawn assumes that we draw in order: top, bottom, left, right. |
| 1745 | // This is different from BoxSide enum order. |
| 1746 | static const BoxSide paintOrder[] = { BSTop, BSBottom, BSLeft, BSRight }; |
| 1747 | |
| 1748 | while (edgesToDraw) { |
| 1749 | // Find undrawn edges sharing a color. |
| 1750 | Color commonColor; |
| 1751 | |
| 1752 | BorderEdgeFlags commonColorEdgeSet = 0; |
| 1753 | for (size_t i = 0; i < sizeof(paintOrder) / sizeof(paintOrder[0]); ++i) { |
| 1754 | BoxSide currSide = paintOrder[i]; |
| 1755 | if (!includesEdge(edgesToDraw, currSide)) |
| 1756 | continue; |
| 1757 | |
| 1758 | bool includeEdge; |
| 1759 | if (!commonColorEdgeSet) { |
| 1760 | commonColor = edges[currSide].color(); |
| 1761 | includeEdge = true; |
| 1762 | } else |
| 1763 | includeEdge = edges[currSide].color() == commonColor; |
| 1764 | |
| 1765 | if (includeEdge) |
| 1766 | commonColorEdgeSet |= edgeFlagForSide(currSide); |
| 1767 | } |
| 1768 | |
| 1769 | bool useTransparencyLayer = includesAdjacentEdges(commonColorEdgeSet) && !commonColor.isOpaque(); |
| 1770 | if (useTransparencyLayer) { |
| 1771 | graphicsContext.beginTransparencyLayer(commonColor.alphaAsFloat()); |
| 1772 | commonColor = commonColor.opaqueColor(); |
| 1773 | } |
| 1774 | |
| 1775 | paintBorderSides(graphicsContext, style, outerBorder, innerBorder, innerBorderAdjustment, edges, commonColorEdgeSet, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, &commonColor); |
| 1776 | |
| 1777 | if (useTransparencyLayer) |
| 1778 | graphicsContext.endTransparencyLayer(); |
| 1779 | |
| 1780 | edgesToDraw &= ~commonColorEdgeSet; |
| 1781 | } |
| 1782 | } |
| 1783 | |
| 1784 | void RenderBoxModelObject::paintBorder(const PaintInfo& info, const LayoutRect& rect, const RenderStyle& style, |
| 1785 | BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) |
| 1786 | { |
| 1787 | GraphicsContext& graphicsContext = info.context(); |
| 1788 | |
| 1789 | if (graphicsContext.paintingDisabled()) |
| 1790 | return; |
| 1791 | |
| 1792 | if (rect.isEmpty()) |
| 1793 | return; |
| 1794 | |
| 1795 | auto rectToClipOut = paintRectToClipOutFromBorder(rect); |
| 1796 | bool appliedClipAlready = !rectToClipOut.isEmpty(); |
| 1797 | GraphicsContextStateSaver stateSave(graphicsContext, appliedClipAlready); |
| 1798 | if (!rectToClipOut.isEmpty()) |
| 1799 | graphicsContext.clipOut(snapRectToDevicePixels(rectToClipOut, document().deviceScaleFactor())); |
| 1800 | |
| 1801 | // border-image is not affected by border-radius. |
| 1802 | if (paintNinePieceImage(graphicsContext, rect, style, style.borderImage())) |
| 1803 | return; |
| 1804 | |
| 1805 | BorderEdge edges[4]; |
| 1806 | BorderEdge::getBorderEdgeInfo(edges, style, document().deviceScaleFactor(), includeLogicalLeftEdge, includeLogicalRightEdge); |
| 1807 | RoundedRect outerBorder = style.getRoundedBorderFor(rect, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 1808 | RoundedRect innerBorder = style.getRoundedInnerBorderFor(borderInnerRectAdjustedForBleedAvoidance(graphicsContext, rect, bleedAvoidance), includeLogicalLeftEdge, includeLogicalRightEdge); |
| 1809 | |
| 1810 | // If no borders intersects with the dirty area, we can skip the border painting. |
| 1811 | if (innerBorder.contains(info.rect)) |
| 1812 | return; |
| 1813 | |
| 1814 | bool haveAlphaColor = false; |
| 1815 | bool haveAllSolidEdges = true; |
| 1816 | bool haveAllDoubleEdges = true; |
| 1817 | int numEdgesVisible = 4; |
| 1818 | bool allEdgesShareColor = true; |
| 1819 | int firstVisibleEdge = -1; |
| 1820 | BorderEdgeFlags edgesToDraw = 0; |
| 1821 | |
| 1822 | for (int i = BSTop; i <= BSLeft; ++i) { |
| 1823 | const BorderEdge& currEdge = edges[i]; |
| 1824 | |
| 1825 | if (edges[i].shouldRender()) |
| 1826 | edgesToDraw |= edgeFlagForSide(static_cast<BoxSide>(i)); |
| 1827 | |
| 1828 | if (currEdge.presentButInvisible()) { |
| 1829 | --numEdgesVisible; |
| 1830 | allEdgesShareColor = false; |
| 1831 | continue; |
| 1832 | } |
| 1833 | |
| 1834 | if (!currEdge.widthForPainting()) { |
| 1835 | --numEdgesVisible; |
| 1836 | continue; |
| 1837 | } |
| 1838 | |
| 1839 | if (firstVisibleEdge == -1) |
| 1840 | firstVisibleEdge = i; |
| 1841 | else if (currEdge.color() != edges[firstVisibleEdge].color()) |
| 1842 | allEdgesShareColor = false; |
| 1843 | |
| 1844 | if (!currEdge.color().isOpaque()) |
| 1845 | haveAlphaColor = true; |
| 1846 | |
| 1847 | if (currEdge.style() != BorderStyle::Solid) |
| 1848 | haveAllSolidEdges = false; |
| 1849 | |
| 1850 | if (currEdge.style() != BorderStyle::Double) |
| 1851 | haveAllDoubleEdges = false; |
| 1852 | } |
| 1853 | |
| 1854 | // If no corner intersects the clip region, we can pretend outerBorder is |
| 1855 | // rectangular to improve performance. |
| 1856 | if (haveAllSolidEdges && outerBorder.isRounded() && allCornersClippedOut(outerBorder, info.rect)) |
| 1857 | outerBorder.setRadii(RoundedRect::Radii()); |
| 1858 | |
| 1859 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 1860 | // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787 |
| 1861 | if ((haveAllSolidEdges || haveAllDoubleEdges) && allEdgesShareColor && innerBorder.isRenderable()) { |
| 1862 | // Fast path for drawing all solid edges and all unrounded double edges |
| 1863 | if (numEdgesVisible == 4 && (outerBorder.isRounded() || haveAlphaColor) |
| 1864 | && (haveAllSolidEdges || (!outerBorder.isRounded() && !innerBorder.isRounded()))) { |
| 1865 | Path path; |
| 1866 | |
| 1867 | FloatRoundedRect pixelSnappedOuterBorder = outerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 1868 | if (pixelSnappedOuterBorder.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) |
| 1869 | path.addRoundedRect(pixelSnappedOuterBorder); |
| 1870 | else |
| 1871 | path.addRect(pixelSnappedOuterBorder.rect()); |
| 1872 | |
| 1873 | if (haveAllDoubleEdges) { |
| 1874 | LayoutRect innerThirdRect = outerBorder.rect(); |
| 1875 | LayoutRect outerThirdRect = outerBorder.rect(); |
| 1876 | for (int side = BSTop; side <= BSLeft; ++side) { |
| 1877 | LayoutUnit outerWidth; |
| 1878 | LayoutUnit innerWidth; |
| 1879 | edges[side].getDoubleBorderStripeWidths(outerWidth, innerWidth); |
| 1880 | |
| 1881 | if (side == BSTop) { |
| 1882 | innerThirdRect.shiftYEdgeTo(innerThirdRect.y() + innerWidth); |
| 1883 | outerThirdRect.shiftYEdgeTo(outerThirdRect.y() + outerWidth); |
| 1884 | } else if (side == BSBottom) { |
| 1885 | innerThirdRect.setHeight(innerThirdRect.height() - innerWidth); |
| 1886 | outerThirdRect.setHeight(outerThirdRect.height() - outerWidth); |
| 1887 | } else if (side == BSLeft) { |
| 1888 | innerThirdRect.shiftXEdgeTo(innerThirdRect.x() + innerWidth); |
| 1889 | outerThirdRect.shiftXEdgeTo(outerThirdRect.x() + outerWidth); |
| 1890 | } else { |
| 1891 | innerThirdRect.setWidth(innerThirdRect.width() - innerWidth); |
| 1892 | outerThirdRect.setWidth(outerThirdRect.width() - outerWidth); |
| 1893 | } |
| 1894 | } |
| 1895 | |
| 1896 | FloatRoundedRect pixelSnappedOuterThird = outerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 1897 | pixelSnappedOuterThird.setRect(snapRectToDevicePixels(outerThirdRect, deviceScaleFactor)); |
| 1898 | |
| 1899 | if (pixelSnappedOuterThird.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) |
| 1900 | path.addRoundedRect(pixelSnappedOuterThird); |
| 1901 | else |
| 1902 | path.addRect(pixelSnappedOuterThird.rect()); |
| 1903 | |
| 1904 | FloatRoundedRect pixelSnappedInnerThird = innerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 1905 | pixelSnappedInnerThird.setRect(snapRectToDevicePixels(innerThirdRect, deviceScaleFactor)); |
| 1906 | if (pixelSnappedInnerThird.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer) |
| 1907 | path.addRoundedRect(pixelSnappedInnerThird); |
| 1908 | else |
| 1909 | path.addRect(pixelSnappedInnerThird.rect()); |
| 1910 | } |
| 1911 | |
| 1912 | FloatRoundedRect pixelSnappedInnerBorder = innerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 1913 | if (pixelSnappedInnerBorder.isRounded()) |
| 1914 | path.addRoundedRect(pixelSnappedInnerBorder); |
| 1915 | else |
| 1916 | path.addRect(pixelSnappedInnerBorder.rect()); |
| 1917 | |
| 1918 | graphicsContext.setFillRule(WindRule::EvenOdd); |
| 1919 | graphicsContext.setFillColor(edges[firstVisibleEdge].color()); |
| 1920 | graphicsContext.fillPath(path); |
| 1921 | return; |
| 1922 | } |
| 1923 | // Avoid creating transparent layers |
| 1924 | if (haveAllSolidEdges && numEdgesVisible != 4 && !outerBorder.isRounded() && haveAlphaColor) { |
| 1925 | Path path; |
| 1926 | |
| 1927 | for (int i = BSTop; i <= BSLeft; ++i) { |
| 1928 | const BorderEdge& currEdge = edges[i]; |
| 1929 | if (currEdge.shouldRender()) { |
| 1930 | LayoutRect sideRect = calculateSideRect(outerBorder, edges, i); |
| 1931 | path.addRect(sideRect); |
| 1932 | } |
| 1933 | } |
| 1934 | |
| 1935 | graphicsContext.setFillRule(WindRule::NonZero); |
| 1936 | graphicsContext.setFillColor(edges[firstVisibleEdge].color()); |
| 1937 | graphicsContext.fillPath(path); |
| 1938 | return; |
| 1939 | } |
| 1940 | } |
| 1941 | |
| 1942 | bool clipToOuterBorder = outerBorder.isRounded(); |
| 1943 | GraphicsContextStateSaver stateSaver(graphicsContext, clipToOuterBorder && !appliedClipAlready); |
| 1944 | if (clipToOuterBorder) { |
| 1945 | // Clip to the inner and outer radii rects. |
| 1946 | if (bleedAvoidance != BackgroundBleedUseTransparencyLayer) |
| 1947 | graphicsContext.clipRoundedRect(outerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); |
| 1948 | // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787 |
| 1949 | // The inside will be clipped out later (in clipBorderSideForComplexInnerPath) |
| 1950 | if (innerBorder.isRenderable()) |
| 1951 | graphicsContext.clipOutRoundedRect(innerBorder.pixelSnappedRoundedRectForPainting(deviceScaleFactor)); |
| 1952 | } |
| 1953 | |
| 1954 | // If only one edge visible antialiasing doesn't create seams |
| 1955 | bool antialias = shouldAntialiasLines(graphicsContext) || numEdgesVisible == 1; |
| 1956 | RoundedRect unadjustedInnerBorder = (bleedAvoidance == BackgroundBleedBackgroundOverBorder) ? style.getRoundedInnerBorderFor(rect, includeLogicalLeftEdge, includeLogicalRightEdge) : innerBorder; |
| 1957 | IntPoint innerBorderAdjustment(innerBorder.rect().x() - unadjustedInnerBorder.rect().x(), innerBorder.rect().y() - unadjustedInnerBorder.rect().y()); |
| 1958 | if (haveAlphaColor) |
| 1959 | paintTranslucentBorderSides(graphicsContext, style, outerBorder, unadjustedInnerBorder, innerBorderAdjustment, edges, edgesToDraw, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias); |
| 1960 | else |
| 1961 | paintBorderSides(graphicsContext, style, outerBorder, unadjustedInnerBorder, innerBorderAdjustment, edges, edgesToDraw, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias); |
| 1962 | } |
| 1963 | |
| 1964 | void RenderBoxModelObject::drawBoxSideFromPath(GraphicsContext& graphicsContext, const LayoutRect& borderRect, const Path& borderPath, const BorderEdge edges[], |
| 1965 | float thickness, float drawThickness, BoxSide side, const RenderStyle& style, Color color, BorderStyle borderStyle, BackgroundBleedAvoidance bleedAvoidance, |
| 1966 | bool includeLogicalLeftEdge, bool includeLogicalRightEdge) |
| 1967 | { |
| 1968 | if (thickness <= 0) |
| 1969 | return; |
| 1970 | |
| 1971 | if (borderStyle == BorderStyle::Double && thickness < 3) |
| 1972 | borderStyle = BorderStyle::Solid; |
| 1973 | |
| 1974 | switch (borderStyle) { |
| 1975 | case BorderStyle::None: |
| 1976 | case BorderStyle::Hidden: |
| 1977 | return; |
| 1978 | case BorderStyle::Dotted: |
| 1979 | case BorderStyle::Dashed: { |
| 1980 | graphicsContext.setStrokeColor(color); |
| 1981 | |
| 1982 | // The stroke is doubled here because the provided path is the |
| 1983 | // outside edge of the border so half the stroke is clipped off. |
| 1984 | // The extra multiplier is so that the clipping mask can antialias |
| 1985 | // the edges to prevent jaggies. |
| 1986 | graphicsContext.setStrokeThickness(drawThickness * 2 * 1.1f); |
| 1987 | graphicsContext.setStrokeStyle(borderStyle == BorderStyle::Dashed ? DashedStroke : DottedStroke); |
| 1988 | |
| 1989 | // If the number of dashes that fit in the path is odd and non-integral then we |
| 1990 | // will have an awkwardly-sized dash at the end of the path. To try to avoid that |
| 1991 | // here, we simply make the whitespace dashes ever so slightly bigger. |
| 1992 | // FIXME: This could be even better if we tried to manipulate the dash offset |
| 1993 | // and possibly the gapLength to get the corners dash-symmetrical. |
| 1994 | float dashLength = thickness * ((borderStyle == BorderStyle::Dashed) ? 3.0f : 1.0f); |
| 1995 | float gapLength = dashLength; |
| 1996 | float numberOfDashes = borderPath.length() / dashLength; |
| 1997 | // Don't try to show dashes if we have less than 2 dashes + 2 gaps. |
| 1998 | // FIXME: should do this test per side. |
| 1999 | if (numberOfDashes >= 4) { |
| 2000 | bool evenNumberOfFullDashes = !((int)numberOfDashes % 2); |
| 2001 | bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes); |
| 2002 | if (!evenNumberOfFullDashes && !integralNumberOfDashes) { |
| 2003 | float numberOfGaps = numberOfDashes / 2; |
| 2004 | gapLength += (dashLength / numberOfGaps); |
| 2005 | } |
| 2006 | |
| 2007 | DashArray lineDash; |
| 2008 | lineDash.append(dashLength); |
| 2009 | lineDash.append(gapLength); |
| 2010 | graphicsContext.setLineDash(lineDash, dashLength); |
| 2011 | } |
| 2012 | |
| 2013 | // FIXME: stroking the border path causes issues with tight corners: |
| 2014 | // https://bugs.webkit.org/show_bug.cgi?id=58711 |
| 2015 | // Also, to get the best appearance we should stroke a path between the two borders. |
| 2016 | graphicsContext.strokePath(borderPath); |
| 2017 | return; |
| 2018 | } |
| 2019 | case BorderStyle::Double: { |
| 2020 | // Get the inner border rects for both the outer border line and the inner border line |
| 2021 | LayoutUnit outerBorderTopWidth; |
| 2022 | LayoutUnit innerBorderTopWidth; |
| 2023 | edges[BSTop].getDoubleBorderStripeWidths(outerBorderTopWidth, innerBorderTopWidth); |
| 2024 | |
| 2025 | LayoutUnit outerBorderRightWidth; |
| 2026 | LayoutUnit innerBorderRightWidth; |
| 2027 | edges[BSRight].getDoubleBorderStripeWidths(outerBorderRightWidth, innerBorderRightWidth); |
| 2028 | |
| 2029 | LayoutUnit outerBorderBottomWidth; |
| 2030 | LayoutUnit innerBorderBottomWidth; |
| 2031 | edges[BSBottom].getDoubleBorderStripeWidths(outerBorderBottomWidth, innerBorderBottomWidth); |
| 2032 | |
| 2033 | LayoutUnit outerBorderLeftWidth; |
| 2034 | LayoutUnit innerBorderLeftWidth; |
| 2035 | edges[BSLeft].getDoubleBorderStripeWidths(outerBorderLeftWidth, innerBorderLeftWidth); |
| 2036 | |
| 2037 | // Draw inner border line |
| 2038 | { |
| 2039 | GraphicsContextStateSaver stateSaver(graphicsContext); |
| 2040 | RoundedRect innerClip = style.getRoundedInnerBorderFor(borderRect, |
| 2041 | innerBorderTopWidth, innerBorderBottomWidth, innerBorderLeftWidth, innerBorderRightWidth, |
| 2042 | includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2043 | |
| 2044 | graphicsContext.clipRoundedRect(FloatRoundedRect(innerClip)); |
| 2045 | drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, BorderStyle::Solid, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2046 | } |
| 2047 | |
| 2048 | // Draw outer border line |
| 2049 | { |
| 2050 | GraphicsContextStateSaver stateSaver(graphicsContext); |
| 2051 | LayoutRect outerRect = borderRect; |
| 2052 | if (bleedAvoidance == BackgroundBleedUseTransparencyLayer) { |
| 2053 | outerRect.inflate(1); |
| 2054 | ++outerBorderTopWidth; |
| 2055 | ++outerBorderBottomWidth; |
| 2056 | ++outerBorderLeftWidth; |
| 2057 | ++outerBorderRightWidth; |
| 2058 | } |
| 2059 | |
| 2060 | RoundedRect outerClip = style.getRoundedInnerBorderFor(outerRect, |
| 2061 | outerBorderTopWidth, outerBorderBottomWidth, outerBorderLeftWidth, outerBorderRightWidth, |
| 2062 | includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2063 | graphicsContext.clipOutRoundedRect(FloatRoundedRect(outerClip)); |
| 2064 | drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, BorderStyle::Solid, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2065 | } |
| 2066 | return; |
| 2067 | } |
| 2068 | case BorderStyle::Ridge: |
| 2069 | case BorderStyle::Groove: |
| 2070 | { |
| 2071 | BorderStyle s1; |
| 2072 | BorderStyle s2; |
| 2073 | if (borderStyle == BorderStyle::Groove) { |
| 2074 | s1 = BorderStyle::Inset; |
| 2075 | s2 = BorderStyle::Outset; |
| 2076 | } else { |
| 2077 | s1 = BorderStyle::Outset; |
| 2078 | s2 = BorderStyle::Inset; |
| 2079 | } |
| 2080 | |
| 2081 | // Paint full border |
| 2082 | drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s1, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2083 | |
| 2084 | // Paint inner only |
| 2085 | GraphicsContextStateSaver stateSaver(graphicsContext); |
| 2086 | LayoutUnit topWidth = edges[BSTop].widthForPainting() / 2; |
| 2087 | LayoutUnit bottomWidth = edges[BSBottom].widthForPainting() / 2; |
| 2088 | LayoutUnit leftWidth = edges[BSLeft].widthForPainting() / 2; |
| 2089 | LayoutUnit rightWidth = edges[BSRight].widthForPainting() / 2; |
| 2090 | |
| 2091 | RoundedRect clipRect = style.getRoundedInnerBorderFor(borderRect, |
| 2092 | topWidth, bottomWidth, leftWidth, rightWidth, |
| 2093 | includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2094 | |
| 2095 | graphicsContext.clipRoundedRect(FloatRoundedRect(clipRect)); |
| 2096 | drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s2, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2097 | return; |
| 2098 | } |
| 2099 | case BorderStyle::Inset: |
| 2100 | case BorderStyle::Outset: |
| 2101 | calculateBorderStyleColor(borderStyle, side, color); |
| 2102 | break; |
| 2103 | default: |
| 2104 | break; |
| 2105 | } |
| 2106 | |
| 2107 | graphicsContext.setStrokeStyle(NoStroke); |
| 2108 | graphicsContext.setFillColor(color); |
| 2109 | graphicsContext.drawRect(snapRectToDevicePixels(borderRect, document().deviceScaleFactor())); |
| 2110 | } |
| 2111 | |
| 2112 | void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext& graphicsContext, const RoundedRect& outerBorder, const RoundedRect& innerBorder, |
| 2113 | BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches) |
| 2114 | { |
| 2115 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 2116 | const FloatRect& outerRect = snapRectToDevicePixels(outerBorder.rect(), deviceScaleFactor); |
| 2117 | const FloatRect& innerRect = snapRectToDevicePixels(innerBorder.rect(), deviceScaleFactor); |
| 2118 | |
| 2119 | // For each side, create a quad that encompasses all parts of that side that may draw, |
| 2120 | // including areas inside the innerBorder. |
| 2121 | // |
| 2122 | // 0----------------3 |
| 2123 | // 0 \ / 0 |
| 2124 | // |\ 1----------- 2 /| |
| 2125 | // | 1 1 | |
| 2126 | // | | | | |
| 2127 | // | | | | |
| 2128 | // | 2 2 | |
| 2129 | // |/ 1------------2 \| |
| 2130 | // 3 / \ 3 |
| 2131 | // 0----------------3 |
| 2132 | // |
| 2133 | Vector<FloatPoint> quad; |
| 2134 | quad.reserveInitialCapacity(4); |
| 2135 | switch (side) { |
| 2136 | case BSTop: |
| 2137 | quad.uncheckedAppend(outerRect.minXMinYCorner()); |
| 2138 | quad.uncheckedAppend(innerRect.minXMinYCorner()); |
| 2139 | quad.uncheckedAppend(innerRect.maxXMinYCorner()); |
| 2140 | quad.uncheckedAppend(outerRect.maxXMinYCorner()); |
| 2141 | |
| 2142 | if (!innerBorder.radii().topLeft().isZero()) |
| 2143 | findIntersection(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), innerRect.minXMaxYCorner(), innerRect.maxXMinYCorner(), quad[1]); |
| 2144 | |
| 2145 | if (!innerBorder.radii().topRight().isZero()) |
| 2146 | findIntersection(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), innerRect.minXMinYCorner(), innerRect.maxXMaxYCorner(), quad[2]); |
| 2147 | break; |
| 2148 | |
| 2149 | case BSLeft: |
| 2150 | quad.uncheckedAppend(outerRect.minXMinYCorner()); |
| 2151 | quad.uncheckedAppend(innerRect.minXMinYCorner()); |
| 2152 | quad.uncheckedAppend(innerRect.minXMaxYCorner()); |
| 2153 | quad.uncheckedAppend(outerRect.minXMaxYCorner()); |
| 2154 | |
| 2155 | if (!innerBorder.radii().topLeft().isZero()) |
| 2156 | findIntersection(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), innerRect.minXMaxYCorner(), innerRect.maxXMinYCorner(), quad[1]); |
| 2157 | |
| 2158 | if (!innerBorder.radii().bottomLeft().isZero()) |
| 2159 | findIntersection(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), innerRect.minXMinYCorner(), innerRect.maxXMaxYCorner(), quad[2]); |
| 2160 | break; |
| 2161 | |
| 2162 | case BSBottom: |
| 2163 | quad.uncheckedAppend(outerRect.minXMaxYCorner()); |
| 2164 | quad.uncheckedAppend(innerRect.minXMaxYCorner()); |
| 2165 | quad.uncheckedAppend(innerRect.maxXMaxYCorner()); |
| 2166 | quad.uncheckedAppend(outerRect.maxXMaxYCorner()); |
| 2167 | |
| 2168 | if (!innerBorder.radii().bottomLeft().isZero()) |
| 2169 | findIntersection(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), innerRect.minXMinYCorner(), innerRect.maxXMaxYCorner(), quad[1]); |
| 2170 | |
| 2171 | if (!innerBorder.radii().bottomRight().isZero()) |
| 2172 | findIntersection(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), innerRect.maxXMinYCorner(), innerRect.minXMaxYCorner(), quad[2]); |
| 2173 | break; |
| 2174 | |
| 2175 | case BSRight: |
| 2176 | quad.uncheckedAppend(outerRect.maxXMinYCorner()); |
| 2177 | quad.uncheckedAppend(innerRect.maxXMinYCorner()); |
| 2178 | quad.uncheckedAppend(innerRect.maxXMaxYCorner()); |
| 2179 | quad.uncheckedAppend(outerRect.maxXMaxYCorner()); |
| 2180 | |
| 2181 | if (!innerBorder.radii().topRight().isZero()) |
| 2182 | findIntersection(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), innerRect.minXMinYCorner(), innerRect.maxXMaxYCorner(), quad[1]); |
| 2183 | |
| 2184 | if (!innerBorder.radii().bottomRight().isZero()) |
| 2185 | findIntersection(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), innerRect.maxXMinYCorner(), innerRect.minXMaxYCorner(), quad[2]); |
| 2186 | break; |
| 2187 | } |
| 2188 | |
| 2189 | // If the border matches both of its adjacent sides, don't anti-alias the clip, and |
| 2190 | // if neither side matches, anti-alias the clip. |
| 2191 | if (firstEdgeMatches == secondEdgeMatches) { |
| 2192 | bool wasAntialiased = graphicsContext.shouldAntialias(); |
| 2193 | graphicsContext.setShouldAntialias(!firstEdgeMatches); |
| 2194 | graphicsContext.clipPath(Path::polygonPathFromPoints(quad), WindRule::NonZero); |
| 2195 | graphicsContext.setShouldAntialias(wasAntialiased); |
| 2196 | return; |
| 2197 | } |
| 2198 | |
| 2199 | // Square off the end which shouldn't be affected by antialiasing, and clip. |
| 2200 | Vector<FloatPoint> firstQuad = { |
| 2201 | quad[0], |
| 2202 | quad[1], |
| 2203 | quad[2], |
| 2204 | side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y()) : FloatPoint(quad[2].x(), quad[3].y()), |
| 2205 | quad[3] |
| 2206 | }; |
| 2207 | bool wasAntialiased = graphicsContext.shouldAntialias(); |
| 2208 | graphicsContext.setShouldAntialias(!firstEdgeMatches); |
| 2209 | graphicsContext.clipPath(Path::polygonPathFromPoints(firstQuad), WindRule::NonZero); |
| 2210 | |
| 2211 | Vector<FloatPoint> secondQuad = { |
| 2212 | quad[0], |
| 2213 | side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y()) : FloatPoint(quad[1].x(), quad[0].y()), |
| 2214 | quad[1], |
| 2215 | quad[2], |
| 2216 | quad[3] |
| 2217 | }; |
| 2218 | // Antialiasing affects the second side. |
| 2219 | graphicsContext.setShouldAntialias(!secondEdgeMatches); |
| 2220 | graphicsContext.clipPath(Path::polygonPathFromPoints(secondQuad), WindRule::NonZero); |
| 2221 | |
| 2222 | graphicsContext.setShouldAntialias(wasAntialiased); |
| 2223 | } |
| 2224 | |
| 2225 | bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const |
| 2226 | { |
| 2227 | BorderEdge edges[4]; |
| 2228 | BorderEdge::getBorderEdgeInfo(edges, style(), document().deviceScaleFactor()); |
| 2229 | |
| 2230 | for (int i = BSTop; i <= BSLeft; ++i) { |
| 2231 | const BorderEdge& currEdge = edges[i]; |
| 2232 | // FIXME: for vertical text |
| 2233 | float axisScale = (i == BSTop || i == BSBottom) ? contextScale.height() : contextScale.width(); |
| 2234 | if (!currEdge.obscuresBackgroundEdge(axisScale)) |
| 2235 | return false; |
| 2236 | } |
| 2237 | |
| 2238 | return true; |
| 2239 | } |
| 2240 | |
| 2241 | bool RenderBoxModelObject::borderObscuresBackground() const |
| 2242 | { |
| 2243 | if (!style().hasBorder()) |
| 2244 | return false; |
| 2245 | |
| 2246 | // Bail if we have any border-image for now. We could look at the image alpha to improve this. |
| 2247 | if (style().borderImage().image()) |
| 2248 | return false; |
| 2249 | |
| 2250 | BorderEdge edges[4]; |
| 2251 | BorderEdge::getBorderEdgeInfo(edges, style(), document().deviceScaleFactor()); |
| 2252 | |
| 2253 | for (int i = BSTop; i <= BSLeft; ++i) { |
| 2254 | const BorderEdge& currEdge = edges[i]; |
| 2255 | if (!currEdge.obscuresBackground()) |
| 2256 | return false; |
| 2257 | } |
| 2258 | |
| 2259 | return true; |
| 2260 | } |
| 2261 | |
| 2262 | bool RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(const LayoutPoint&, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* inlineFlowBox) const |
| 2263 | { |
| 2264 | if (bleedAvoidance != BackgroundBleedNone) |
| 2265 | return false; |
| 2266 | |
| 2267 | if (style().hasAppearance()) |
| 2268 | return false; |
| 2269 | |
| 2270 | bool hasOneNormalBoxShadow = false; |
| 2271 | for (const ShadowData* currentShadow = style().boxShadow(); currentShadow; currentShadow = currentShadow->next()) { |
| 2272 | if (currentShadow->style() != Normal) |
| 2273 | continue; |
| 2274 | |
| 2275 | if (hasOneNormalBoxShadow) |
| 2276 | return false; |
| 2277 | hasOneNormalBoxShadow = true; |
| 2278 | |
| 2279 | if (currentShadow->spread()) |
| 2280 | return false; |
| 2281 | } |
| 2282 | |
| 2283 | if (!hasOneNormalBoxShadow) |
| 2284 | return false; |
| 2285 | |
| 2286 | Color backgroundColor = style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); |
| 2287 | if (!backgroundColor.isOpaque()) |
| 2288 | return false; |
| 2289 | |
| 2290 | auto* lastBackgroundLayer = &style().backgroundLayers(); |
| 2291 | while (auto* next = lastBackgroundLayer->next()) |
| 2292 | lastBackgroundLayer = next; |
| 2293 | |
| 2294 | if (lastBackgroundLayer->clip() != FillBox::Border) |
| 2295 | return false; |
| 2296 | |
| 2297 | if (lastBackgroundLayer->image() && style().hasBorderRadius()) |
| 2298 | return false; |
| 2299 | |
| 2300 | if (inlineFlowBox && !inlineFlowBox->boxShadowCanBeAppliedToBackground(*lastBackgroundLayer)) |
| 2301 | return false; |
| 2302 | |
| 2303 | if (hasOverflowClip() && lastBackgroundLayer->attachment() == FillAttachment::LocalBackground) |
| 2304 | return false; |
| 2305 | |
| 2306 | return true; |
| 2307 | } |
| 2308 | |
| 2309 | static inline LayoutRect areaCastingShadowInHole(const LayoutRect& holeRect, int shadowExtent, int shadowSpread, const IntSize& shadowOffset) |
| 2310 | { |
| 2311 | LayoutRect bounds(holeRect); |
| 2312 | |
| 2313 | bounds.inflate(shadowExtent); |
| 2314 | |
| 2315 | if (shadowSpread < 0) |
| 2316 | bounds.inflate(-shadowSpread); |
| 2317 | |
| 2318 | LayoutRect offsetBounds = bounds; |
| 2319 | offsetBounds.move(-shadowOffset); |
| 2320 | return unionRect(bounds, offsetBounds); |
| 2321 | } |
| 2322 | |
| 2323 | void RenderBoxModelObject::paintBoxShadow(const PaintInfo& info, const LayoutRect& paintRect, const RenderStyle& style, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) |
| 2324 | { |
| 2325 | // FIXME: Deal with border-image. Would be great to use border-image as a mask. |
| 2326 | GraphicsContext& context = info.context(); |
| 2327 | if (context.paintingDisabled() || !style.boxShadow()) |
| 2328 | return; |
| 2329 | |
| 2330 | RoundedRect border = (shadowStyle == Inset) ? style.getRoundedInnerBorderFor(paintRect, includeLogicalLeftEdge, includeLogicalRightEdge) |
| 2331 | : style.getRoundedBorderFor(paintRect, includeLogicalLeftEdge, includeLogicalRightEdge); |
| 2332 | |
| 2333 | bool hasBorderRadius = style.hasBorderRadius(); |
| 2334 | bool isHorizontal = style.isHorizontalWritingMode(); |
| 2335 | float deviceScaleFactor = document().deviceScaleFactor(); |
| 2336 | |
| 2337 | bool hasOpaqueBackground = style.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor).isOpaque(); |
| 2338 | for (const ShadowData* shadow = style.boxShadow(); shadow; shadow = shadow->next()) { |
| 2339 | if (shadow->style() != shadowStyle) |
| 2340 | continue; |
| 2341 | |
| 2342 | // FIXME: Add subpixel support for the shadow values. Soon after the shadow offset becomes fractional, |
| 2343 | // all the early snappings here need to be pushed to the actual painting operations. |
| 2344 | IntSize shadowOffset(shadow->x(), shadow->y()); |
| 2345 | int shadowRadius = shadow->radius(); |
| 2346 | int shadowPaintingExtent = shadow->paintingExtent(); |
| 2347 | int shadowSpread = shadow->spread(); |
| 2348 | |
| 2349 | if (shadowOffset.isZero() && !shadowRadius && !shadowSpread) |
| 2350 | continue; |
| 2351 | |
| 2352 | Color shadowColor = style.colorByApplyingColorFilter(shadow->color()); |
| 2353 | |
| 2354 | if (shadow->style() == Normal) { |
| 2355 | RoundedRect fillRect = border; |
| 2356 | fillRect.inflate(shadowSpread); |
| 2357 | if (fillRect.isEmpty()) |
| 2358 | continue; |
| 2359 | |
| 2360 | FloatRect pixelSnappedShadowRect = snapRectToDevicePixels(border.rect(), deviceScaleFactor); |
| 2361 | pixelSnappedShadowRect.inflate(shadowPaintingExtent + shadowSpread); |
| 2362 | pixelSnappedShadowRect.move(shadowOffset); |
| 2363 | |
| 2364 | GraphicsContextStateSaver stateSaver(context); |
| 2365 | context.clip(pixelSnappedShadowRect); |
| 2366 | |
| 2367 | // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not |
| 2368 | // bleed in (due to antialiasing) if the context is transformed. |
| 2369 | IntSize (roundToInt(paintRect.width()) + std::max(0, shadowOffset.width()) + shadowPaintingExtent + 2 * shadowSpread + 1, 0); |
| 2370 | shadowOffset -= extraOffset; |
| 2371 | fillRect.move(extraOffset); |
| 2372 | |
| 2373 | if (shadow->isWebkitBoxShadow()) |
| 2374 | context.setLegacyShadow(shadowOffset, shadowRadius, shadowColor); |
| 2375 | else |
| 2376 | context.setShadow(shadowOffset, shadowRadius, shadowColor); |
| 2377 | |
| 2378 | FloatRoundedRect rectToClipOut = border.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 2379 | FloatRoundedRect pixelSnappedFillRect = fillRect.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 2380 | if (hasBorderRadius) { |
| 2381 | // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time |
| 2382 | // when painting the shadow. On the other hand, it introduces subpixel gaps along the |
| 2383 | // corners. Those are avoided by insetting the clipping path by one pixel. |
| 2384 | if (hasOpaqueBackground) |
| 2385 | rectToClipOut.inflateWithRadii(-1.0f); |
| 2386 | |
| 2387 | if (!rectToClipOut.isEmpty()) |
| 2388 | context.clipOutRoundedRect(rectToClipOut); |
| 2389 | |
| 2390 | RoundedRect influenceRect(LayoutRect(pixelSnappedShadowRect), border.radii()); |
| 2391 | influenceRect.expandRadii(2 * shadowPaintingExtent + shadowSpread); |
| 2392 | |
| 2393 | if (allCornersClippedOut(influenceRect, info.rect)) |
| 2394 | context.fillRect(pixelSnappedFillRect.rect(), Color::black); |
| 2395 | else { |
| 2396 | pixelSnappedFillRect.expandRadii(shadowSpread); |
| 2397 | if (!pixelSnappedFillRect.isRenderable()) |
| 2398 | pixelSnappedFillRect.adjustRadii(); |
| 2399 | context.fillRoundedRect(pixelSnappedFillRect, Color::black); |
| 2400 | } |
| 2401 | } else { |
| 2402 | // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time |
| 2403 | // when painting the shadow. On the other hand, it introduces subpixel gaps along the |
| 2404 | // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path |
| 2405 | // by one pixel. |
| 2406 | if (hasOpaqueBackground) { |
| 2407 | // FIXME: The function to decide on the policy based on the transform should be a named function. |
| 2408 | // FIXME: It's not clear if this check is right. What about integral scale factors? |
| 2409 | AffineTransform transform = context.getCTM(); |
| 2410 | if (transform.a() != 1 || (transform.d() != 1 && transform.d() != -1) || transform.b() || transform.c()) |
| 2411 | rectToClipOut.inflate(-1.0f); |
| 2412 | } |
| 2413 | |
| 2414 | if (!rectToClipOut.isEmpty()) |
| 2415 | context.clipOut(rectToClipOut.rect()); |
| 2416 | context.fillRect(pixelSnappedFillRect.rect(), Color::black); |
| 2417 | } |
| 2418 | } else { |
| 2419 | // Inset shadow. |
| 2420 | FloatRoundedRect pixelSnappedBorderRect = border.pixelSnappedRoundedRectForPainting(deviceScaleFactor); |
| 2421 | FloatRect pixelSnappedHoleRect = pixelSnappedBorderRect.rect(); |
| 2422 | pixelSnappedHoleRect.inflate(-shadowSpread); |
| 2423 | |
| 2424 | if (pixelSnappedHoleRect.isEmpty()) { |
| 2425 | if (hasBorderRadius) |
| 2426 | context.fillRoundedRect(pixelSnappedBorderRect, shadowColor); |
| 2427 | else |
| 2428 | context.fillRect(pixelSnappedBorderRect.rect(), shadowColor); |
| 2429 | continue; |
| 2430 | } |
| 2431 | |
| 2432 | if (!includeLogicalLeftEdge) { |
| 2433 | if (isHorizontal) { |
| 2434 | pixelSnappedHoleRect.move(-std::max(shadowOffset.width(), 0) - shadowPaintingExtent, 0); |
| 2435 | pixelSnappedHoleRect.setWidth(pixelSnappedHoleRect.width() + std::max(shadowOffset.width(), 0) + shadowPaintingExtent); |
| 2436 | } else { |
| 2437 | pixelSnappedHoleRect.move(0, -std::max(shadowOffset.height(), 0) - shadowPaintingExtent); |
| 2438 | pixelSnappedHoleRect.setHeight(pixelSnappedHoleRect.height() + std::max(shadowOffset.height(), 0) + shadowPaintingExtent); |
| 2439 | } |
| 2440 | } |
| 2441 | if (!includeLogicalRightEdge) { |
| 2442 | if (isHorizontal) |
| 2443 | pixelSnappedHoleRect.setWidth(pixelSnappedHoleRect.width() - std::min(shadowOffset.width(), 0) + shadowPaintingExtent); |
| 2444 | else |
| 2445 | pixelSnappedHoleRect.setHeight(pixelSnappedHoleRect.height() - std::min(shadowOffset.height(), 0) + shadowPaintingExtent); |
| 2446 | } |
| 2447 | |
| 2448 | Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255); |
| 2449 | |
| 2450 | FloatRect pixelSnappedOuterRect = snapRectToDevicePixels(areaCastingShadowInHole(LayoutRect(pixelSnappedBorderRect.rect()), shadowPaintingExtent, shadowSpread, shadowOffset), deviceScaleFactor); |
| 2451 | FloatRoundedRect pixelSnappedRoundedHole = FloatRoundedRect(pixelSnappedHoleRect, pixelSnappedBorderRect.radii()); |
| 2452 | |
| 2453 | GraphicsContextStateSaver stateSaver(context); |
| 2454 | if (hasBorderRadius) { |
| 2455 | context.clipRoundedRect(pixelSnappedBorderRect); |
| 2456 | pixelSnappedRoundedHole.shrinkRadii(shadowSpread); |
| 2457 | } else |
| 2458 | context.clip(pixelSnappedBorderRect.rect()); |
| 2459 | |
| 2460 | IntSize (2 * roundToInt(paintRect.width()) + std::max(0, shadowOffset.width()) + shadowPaintingExtent - 2 * shadowSpread + 1, 0); |
| 2461 | context.translate(extraOffset); |
| 2462 | shadowOffset -= extraOffset; |
| 2463 | |
| 2464 | if (shadow->isWebkitBoxShadow()) |
| 2465 | context.setLegacyShadow(shadowOffset, shadowRadius, shadowColor); |
| 2466 | else |
| 2467 | context.setShadow(shadowOffset, shadowRadius, shadowColor); |
| 2468 | |
| 2469 | context.fillRectWithRoundedHole(pixelSnappedOuterRect, pixelSnappedRoundedHole, fillColor); |
| 2470 | } |
| 2471 | } |
| 2472 | } |
| 2473 | |
| 2474 | LayoutUnit RenderBoxModelObject::containingBlockLogicalWidthForContent() const |
| 2475 | { |
| 2476 | if (auto* containingBlock = this->containingBlock()) |
| 2477 | return containingBlock->availableLogicalWidth(); |
| 2478 | return { }; |
| 2479 | } |
| 2480 | |
| 2481 | RenderBoxModelObject* RenderBoxModelObject::continuation() const |
| 2482 | { |
| 2483 | if (!hasContinuationChainNode()) |
| 2484 | return nullptr; |
| 2485 | |
| 2486 | auto& continuationChainNode = *continuationChainNodeMap().get(this); |
| 2487 | if (!continuationChainNode.next) |
| 2488 | return nullptr; |
| 2489 | return continuationChainNode.next->renderer.get(); |
| 2490 | } |
| 2491 | |
| 2492 | RenderInline* RenderBoxModelObject::inlineContinuation() const |
| 2493 | { |
| 2494 | if (!hasContinuationChainNode()) |
| 2495 | return nullptr; |
| 2496 | |
| 2497 | for (auto* next = continuationChainNodeMap().get(this)->next; next; next = next->next) { |
| 2498 | if (is<RenderInline>(*next->renderer)) |
| 2499 | return downcast<RenderInline>(next->renderer.get()); |
| 2500 | } |
| 2501 | return nullptr; |
| 2502 | } |
| 2503 | |
| 2504 | RenderBoxModelObject::ContinuationChainNode* RenderBoxModelObject::continuationChainNode() const |
| 2505 | { |
| 2506 | return continuationChainNodeMap().get(this); |
| 2507 | } |
| 2508 | |
| 2509 | void RenderBoxModelObject::insertIntoContinuationChainAfter(RenderBoxModelObject& afterRenderer) |
| 2510 | { |
| 2511 | ASSERT(isContinuation()); |
| 2512 | ASSERT(!continuationChainNodeMap().contains(this)); |
| 2513 | |
| 2514 | auto& after = afterRenderer.ensureContinuationChainNode(); |
| 2515 | ensureContinuationChainNode().insertAfter(after); |
| 2516 | } |
| 2517 | |
| 2518 | void RenderBoxModelObject::removeFromContinuationChain() |
| 2519 | { |
| 2520 | ASSERT(hasContinuationChainNode()); |
| 2521 | ASSERT(continuationChainNodeMap().contains(this)); |
| 2522 | setHasContinuationChainNode(false); |
| 2523 | continuationChainNodeMap().remove(this); |
| 2524 | } |
| 2525 | |
| 2526 | auto RenderBoxModelObject::ensureContinuationChainNode() -> ContinuationChainNode& |
| 2527 | { |
| 2528 | setHasContinuationChainNode(true); |
| 2529 | return *continuationChainNodeMap().ensure(this, [&] { |
| 2530 | return std::make_unique<ContinuationChainNode>(*this); |
| 2531 | }).iterator->value; |
| 2532 | } |
| 2533 | |
| 2534 | RenderTextFragment* RenderBoxModelObject::firstLetterRemainingText() const |
| 2535 | { |
| 2536 | if (!isFirstLetter()) |
| 2537 | return nullptr; |
| 2538 | return firstLetterRemainingTextMap().get(this).get(); |
| 2539 | } |
| 2540 | |
| 2541 | void RenderBoxModelObject::setFirstLetterRemainingText(RenderTextFragment& remainingText) |
| 2542 | { |
| 2543 | ASSERT(isFirstLetter()); |
| 2544 | firstLetterRemainingTextMap().set(this, makeWeakPtr(remainingText)); |
| 2545 | } |
| 2546 | |
| 2547 | void RenderBoxModelObject::clearFirstLetterRemainingText() |
| 2548 | { |
| 2549 | ASSERT(isFirstLetter()); |
| 2550 | firstLetterRemainingTextMap().remove(this); |
| 2551 | } |
| 2552 | |
| 2553 | LayoutRect RenderBoxModelObject::localCaretRectForEmptyElement(LayoutUnit width, LayoutUnit textIndentOffset) |
| 2554 | { |
| 2555 | ASSERT(!firstChild()); |
| 2556 | |
| 2557 | // FIXME: This does not take into account either :first-line or :first-letter |
| 2558 | // However, as soon as some content is entered, the line boxes will be |
| 2559 | // constructed and this kludge is not called any more. So only the caret size |
| 2560 | // of an empty :first-line'd block is wrong. I think we can live with that. |
| 2561 | const RenderStyle& currentStyle = firstLineStyle(); |
| 2562 | LayoutUnit height = lineHeight(true, currentStyle.isHorizontalWritingMode() ? HorizontalLine : VerticalLine); |
| 2563 | |
| 2564 | enum CaretAlignment { alignLeft, alignRight, alignCenter }; |
| 2565 | |
| 2566 | CaretAlignment alignment = alignLeft; |
| 2567 | |
| 2568 | switch (currentStyle.textAlign()) { |
| 2569 | case TextAlignMode::Left: |
| 2570 | case TextAlignMode::WebKitLeft: |
| 2571 | break; |
| 2572 | case TextAlignMode::Center: |
| 2573 | case TextAlignMode::WebKitCenter: |
| 2574 | alignment = alignCenter; |
| 2575 | break; |
| 2576 | case TextAlignMode::Right: |
| 2577 | case TextAlignMode::WebKitRight: |
| 2578 | alignment = alignRight; |
| 2579 | break; |
| 2580 | case TextAlignMode::Justify: |
| 2581 | case TextAlignMode::Start: |
| 2582 | if (!currentStyle.isLeftToRightDirection()) |
| 2583 | alignment = alignRight; |
| 2584 | break; |
| 2585 | case TextAlignMode::End: |
| 2586 | if (currentStyle.isLeftToRightDirection()) |
| 2587 | alignment = alignRight; |
| 2588 | break; |
| 2589 | } |
| 2590 | |
| 2591 | LayoutUnit x = borderLeft() + paddingLeft(); |
| 2592 | LayoutUnit maxX = width - borderRight() - paddingRight(); |
| 2593 | |
| 2594 | switch (alignment) { |
| 2595 | case alignLeft: |
| 2596 | if (currentStyle.isLeftToRightDirection()) |
| 2597 | x += textIndentOffset; |
| 2598 | break; |
| 2599 | case alignCenter: |
| 2600 | x = (x + maxX) / 2; |
| 2601 | if (currentStyle.isLeftToRightDirection()) |
| 2602 | x += textIndentOffset / 2; |
| 2603 | else |
| 2604 | x -= textIndentOffset / 2; |
| 2605 | break; |
| 2606 | case alignRight: |
| 2607 | x = maxX - caretWidth; |
| 2608 | if (!currentStyle.isLeftToRightDirection()) |
| 2609 | x -= textIndentOffset; |
| 2610 | break; |
| 2611 | } |
| 2612 | x = std::min(x, std::max<LayoutUnit>(maxX - caretWidth, 0)); |
| 2613 | |
| 2614 | LayoutUnit y = paddingTop() + borderTop(); |
| 2615 | |
| 2616 | return currentStyle.isHorizontalWritingMode() ? LayoutRect(x, y, caretWidth, height) : LayoutRect(y, x, height, caretWidth); |
| 2617 | } |
| 2618 | |
| 2619 | bool RenderBoxModelObject::shouldAntialiasLines(GraphicsContext& context) |
| 2620 | { |
| 2621 | // FIXME: We may want to not antialias when scaled by an integral value, |
| 2622 | // and we may want to antialias when translated by a non-integral value. |
| 2623 | return !context.getCTM().isIdentityOrTranslationOrFlipped(); |
| 2624 | } |
| 2625 | |
| 2626 | void RenderBoxModelObject::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const |
| 2627 | { |
| 2628 | RenderElement* container = this->container(); |
| 2629 | if (!container) |
| 2630 | return; |
| 2631 | |
| 2632 | // FIXME: This code is wrong for named flow threads since it only works for content in the first region. |
| 2633 | // We also don't want to run it for multicolumn flow threads, since we can use our knowledge of column |
| 2634 | // geometry to actually get a better result. |
| 2635 | // The point inside a box that's inside a region has its coordinates relative to the region, |
| 2636 | // not the FragmentedFlow that is its container in the RenderObject tree. |
| 2637 | if (is<RenderBox>(*this) && container->isOutOfFlowRenderFragmentedFlow()) { |
| 2638 | RenderFragmentContainer* startFragment = nullptr; |
| 2639 | RenderFragmentContainer* endFragment = nullptr; |
| 2640 | if (downcast<RenderFragmentedFlow>(*container).getFragmentRangeForBox(downcast<RenderBox>(this), startFragment, endFragment)) |
| 2641 | container = startFragment; |
| 2642 | } |
| 2643 | |
| 2644 | container->mapAbsoluteToLocalPoint(mode, transformState); |
| 2645 | |
| 2646 | LayoutSize containerOffset = offsetFromContainer(*container, LayoutPoint()); |
| 2647 | |
| 2648 | bool preserve3D = mode & UseTransforms && (container->style().preserves3D() || style().preserves3D()); |
| 2649 | if (mode & UseTransforms && shouldUseTransformFromContainer(container)) { |
| 2650 | TransformationMatrix t; |
| 2651 | getTransformFromContainer(container, containerOffset, t); |
| 2652 | transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| 2653 | } else |
| 2654 | transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| 2655 | } |
| 2656 | |
| 2657 | bool RenderBoxModelObject::hasRunningAcceleratedAnimations() const |
| 2658 | { |
| 2659 | if (auto* node = element()) { |
| 2660 | if (auto* timeline = node->document().existingTimeline()) |
| 2661 | return timeline->runningAnimationsForElementAreAllAccelerated(*node); |
| 2662 | } |
| 2663 | return false; |
| 2664 | } |
| 2665 | |
| 2666 | } // namespace WebCore |
| 2667 | |