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 | |