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 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 "GraphicsContext.h"
30 #include "HTMLFrameOwnerElement.h"
31 #include "HTMLNames.h"
32 #include "ImageBuffer.h"
33 #include "Path.h"
34 #include "RenderBlock.h"
35 #include "RenderInline.h"
36 #include "RenderLayer.h"
37 #include "RenderView.h"
38 #include <wtf/CurrentTime.h>
39 
40 using namespace std;
41 
42 namespace WebCore {
43 
44 using namespace HTMLNames;
45 
46 bool RenderBoxModelObject::s_wasFloating = false;
47 bool RenderBoxModelObject::s_hadLayer = false;
48 bool RenderBoxModelObject::s_layerWasSelfPainting = false;
49 
50 static const double cInterpolationCutoff = 800. * 800.;
51 static const double cLowQualityTimeThreshold = 0.500; // 500 ms
52 
53 typedef HashMap<const void*, IntSize> LayerSizeMap;
54 typedef HashMap<RenderBoxModelObject*, LayerSizeMap> ObjectLayerSizeMap;
55 
56 // The HashMap for storing continuation pointers.
57 // An inline can be split with blocks occuring in between the inline content.
58 // When this occurs we need a pointer to the next object. We can basically be
59 // split into a sequence of inlines and blocks. The continuation will either be
60 // an anonymous block (that houses other blocks) or it will be an inline flow.
61 // <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as
62 // its continuation but the <b> will just have an inline as its continuation.
63 typedef HashMap<const RenderBoxModelObject*, RenderBoxModelObject*> ContinuationMap;
64 static ContinuationMap* continuationMap = 0;
65 
66 class ImageQualityController {
67     WTF_MAKE_NONCOPYABLE(ImageQualityController); WTF_MAKE_FAST_ALLOCATED;
68 public:
69     ImageQualityController();
70     bool shouldPaintAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const void* layer, const IntSize&);
71     void removeLayer(RenderBoxModelObject*, LayerSizeMap* innerMap, const void* layer);
72     void set(RenderBoxModelObject*, LayerSizeMap* innerMap, const void* layer, const IntSize&);
73     void objectDestroyed(RenderBoxModelObject*);
isEmpty()74     bool isEmpty() { return m_objectLayerSizeMap.isEmpty(); }
75 
76 private:
77     void highQualityRepaintTimerFired(Timer<ImageQualityController>*);
78     void restartTimer();
79 
80     ObjectLayerSizeMap m_objectLayerSizeMap;
81     Timer<ImageQualityController> m_timer;
82     bool m_animatedResizeIsActive;
83 };
84 
ImageQualityController()85 ImageQualityController::ImageQualityController()
86     : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired)
87     , m_animatedResizeIsActive(false)
88 {
89 }
90 
removeLayer(RenderBoxModelObject * object,LayerSizeMap * innerMap,const void * layer)91 void ImageQualityController::removeLayer(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer)
92 {
93     if (innerMap) {
94         innerMap->remove(layer);
95         if (innerMap->isEmpty())
96             objectDestroyed(object);
97     }
98 }
99 
set(RenderBoxModelObject * object,LayerSizeMap * innerMap,const void * layer,const IntSize & size)100 void ImageQualityController::set(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer, const IntSize& size)
101 {
102     if (innerMap)
103         innerMap->set(layer, size);
104     else {
105         LayerSizeMap newInnerMap;
106         newInnerMap.set(layer, size);
107         m_objectLayerSizeMap.set(object, newInnerMap);
108     }
109 }
110 
objectDestroyed(RenderBoxModelObject * object)111 void ImageQualityController::objectDestroyed(RenderBoxModelObject* object)
112 {
113     m_objectLayerSizeMap.remove(object);
114     if (m_objectLayerSizeMap.isEmpty()) {
115         m_animatedResizeIsActive = false;
116         m_timer.stop();
117     }
118 }
119 
highQualityRepaintTimerFired(Timer<ImageQualityController> *)120 void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*)
121 {
122     if (m_animatedResizeIsActive) {
123         m_animatedResizeIsActive = false;
124         for (ObjectLayerSizeMap::iterator it = m_objectLayerSizeMap.begin(); it != m_objectLayerSizeMap.end(); ++it)
125             it->first->repaint();
126     }
127 }
128 
restartTimer()129 void ImageQualityController::restartTimer()
130 {
131     m_timer.startOneShot(cLowQualityTimeThreshold);
132 }
133 
shouldPaintAtLowQuality(GraphicsContext * context,RenderBoxModelObject * object,Image * image,const void * layer,const IntSize & size)134 bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const IntSize& size)
135 {
136     // If the image is not a bitmap image, then none of this is relevant and we just paint at high
137     // quality.
138     if (!image || !image->isBitmapImage() || context->paintingDisabled())
139         return false;
140 
141     // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
142     // is actually being scaled.
143     IntSize imageSize(image->width(), image->height());
144 
145     // Look ourselves up in the hashtables.
146     ObjectLayerSizeMap::iterator i = m_objectLayerSizeMap.find(object);
147     LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->second : 0;
148     IntSize oldSize;
149     bool isFirstResize = true;
150     if (innerMap) {
151         LayerSizeMap::iterator j = innerMap->find(layer);
152         if (j != innerMap->end()) {
153             isFirstResize = false;
154             oldSize = j->second;
155         }
156     }
157 
158     const AffineTransform& currentTransform = context->getCTM();
159     bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
160     if (!contextIsScaled && imageSize == size) {
161         // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
162         removeLayer(object, innerMap, layer);
163         return false;
164     }
165 
166     // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
167     if (object->document()->page()->inLowQualityImageInterpolationMode()) {
168         double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height());
169         if (totalPixels > cInterpolationCutoff)
170             return true;
171     }
172 
173     // If an animated resize is active, paint in low quality and kick the timer ahead.
174     if (m_animatedResizeIsActive) {
175         set(object, innerMap, layer, size);
176         restartTimer();
177         return true;
178     }
179     // If this is the first time resizing this image, or its size is the
180     // same as the last resize, draw at high res, but record the paint
181     // size and set the timer.
182     if (isFirstResize || oldSize == size) {
183         restartTimer();
184         set(object, innerMap, layer, size);
185         return false;
186     }
187     // If the timer is no longer active, draw at high quality and don't
188     // set the timer.
189     if (!m_timer.isActive()) {
190         removeLayer(object, innerMap, layer);
191         return false;
192     }
193     // This object has been resized to two different sizes while the timer
194     // is active, so draw at low quality, set the flag for animated resizes and
195     // the object to the list for high quality redraw.
196     set(object, innerMap, layer, size);
197     m_animatedResizeIsActive = true;
198     restartTimer();
199     return true;
200 }
201 
202 static ImageQualityController* gImageQualityController = 0;
203 
imageQualityController()204 static ImageQualityController* imageQualityController()
205 {
206     if (!gImageQualityController)
207         gImageQualityController = new ImageQualityController;
208 
209     return gImageQualityController;
210 }
211 
setSelectionState(SelectionState s)212 void RenderBoxModelObject::setSelectionState(SelectionState s)
213 {
214     if (selectionState() == s)
215         return;
216 
217     if (s == SelectionInside && selectionState() != SelectionNone)
218         return;
219 
220     if ((s == SelectionStart && selectionState() == SelectionEnd)
221         || (s == SelectionEnd && selectionState() == SelectionStart))
222         RenderObject::setSelectionState(SelectionBoth);
223     else
224         RenderObject::setSelectionState(s);
225 
226     // FIXME:
227     // We should consider whether it is OK propagating to ancestor RenderInlines.
228     // This is a workaround for http://webkit.org/b/32123
229     RenderBlock* cb = containingBlock();
230     if (cb && !cb->isRenderView())
231         cb->setSelectionState(s);
232 }
233 
shouldPaintAtLowQuality(GraphicsContext * context,Image * image,const void * layer,const IntSize & size)234 bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const IntSize& size)
235 {
236     return imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size);
237 }
238 
RenderBoxModelObject(Node * node)239 RenderBoxModelObject::RenderBoxModelObject(Node* node)
240     : RenderObject(node)
241     , m_layer(0)
242 {
243 }
244 
~RenderBoxModelObject()245 RenderBoxModelObject::~RenderBoxModelObject()
246 {
247     // Our layer should have been destroyed and cleared by now
248     ASSERT(!hasLayer());
249     ASSERT(!m_layer);
250     if (gImageQualityController) {
251         gImageQualityController->objectDestroyed(this);
252         if (gImageQualityController->isEmpty()) {
253             delete gImageQualityController;
254             gImageQualityController = 0;
255         }
256     }
257 }
258 
destroyLayer()259 void RenderBoxModelObject::destroyLayer()
260 {
261     ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false)
262     ASSERT(m_layer);
263     m_layer->destroy(renderArena());
264     m_layer = 0;
265 }
266 
destroy()267 void RenderBoxModelObject::destroy()
268 {
269     // This must be done before we destroy the RenderObject.
270     if (m_layer)
271         m_layer->clearClipRects();
272 
273     // A continuation of this RenderObject should be destroyed at subclasses.
274     ASSERT(!continuation());
275 
276     // RenderObject::destroy calls back to destroyLayer() for layer destruction
277     RenderObject::destroy();
278 }
279 
hasSelfPaintingLayer() const280 bool RenderBoxModelObject::hasSelfPaintingLayer() const
281 {
282     return m_layer && m_layer->isSelfPaintingLayer();
283 }
284 
styleWillChange(StyleDifference diff,const RenderStyle * newStyle)285 void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
286 {
287     s_wasFloating = isFloating();
288     s_hadLayer = hasLayer();
289     if (s_hadLayer)
290         s_layerWasSelfPainting = layer()->isSelfPaintingLayer();
291 
292     // If our z-index changes value or our visibility changes,
293     // we need to dirty our stacking context's z-order list.
294     if (style() && newStyle) {
295         if (parent()) {
296             // Do a repaint with the old style first, e.g., for example if we go from
297             // having an outline to not having an outline.
298             if (diff == StyleDifferenceRepaintLayer) {
299                 layer()->repaintIncludingDescendants();
300                 if (!(style()->clip() == newStyle->clip()))
301                     layer()->clearClipRectsIncludingDescendants();
302             } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize())
303                 repaint();
304         }
305 
306         if (diff == StyleDifferenceLayout || diff == StyleDifferenceSimplifiedLayout) {
307             // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could
308             // end up being destroyed.
309             if (hasLayer()) {
310                 if (style()->position() != newStyle->position() ||
311                     style()->zIndex() != newStyle->zIndex() ||
312                     style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
313                     !(style()->clip() == newStyle->clip()) ||
314                     style()->hasClip() != newStyle->hasClip() ||
315                     style()->opacity() != newStyle->opacity() ||
316                     style()->transform() != newStyle->transform())
317                 layer()->repaintIncludingDescendants();
318             } else if (newStyle->hasTransform() || newStyle->opacity() < 1) {
319                 // If we don't have a layer yet, but we are going to get one because of transform or opacity,
320                 //  then we need to repaint the old position of the object.
321                 repaint();
322             }
323         }
324 
325         if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
326                            style()->zIndex() != newStyle->zIndex() ||
327                            style()->visibility() != newStyle->visibility())) {
328             layer()->dirtyStackingContextZOrderLists();
329             if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility())
330                 layer()->dirtyZOrderLists();
331         }
332     }
333 
334     RenderObject::styleWillChange(diff, newStyle);
335 }
336 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)337 void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
338 {
339     RenderObject::styleDidChange(diff, oldStyle);
340     updateBoxModelInfoFromStyle();
341 
342     if (requiresLayer()) {
343         if (!layer()) {
344             if (s_wasFloating && isFloating())
345                 setChildNeedsLayout(true);
346             m_layer = new (renderArena()) RenderLayer(this);
347             setHasLayer(true);
348             m_layer->insertOnlyThisLayer();
349             if (parent() && !needsLayout() && containingBlock()) {
350                 m_layer->setNeedsFullRepaint();
351                 m_layer->updateLayerPositions();
352             }
353         }
354     } else if (layer() && layer()->parent()) {
355         setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit.
356         setHasReflection(false);
357         m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer
358         if (s_wasFloating && isFloating())
359             setChildNeedsLayout(true);
360     }
361 
362     if (layer()) {
363         layer()->styleChanged(diff, oldStyle);
364         if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting)
365             setChildNeedsLayout(true);
366     }
367 }
368 
updateBoxModelInfoFromStyle()369 void RenderBoxModelObject::updateBoxModelInfoFromStyle()
370 {
371     // Set the appropriate bits for a box model object.  Since all bits are cleared in styleWillChange,
372     // we only check for bits that could possibly be set to true.
373     setHasBoxDecorations(hasBackground() || style()->hasBorder() || style()->hasAppearance() || style()->boxShadow());
374     setInline(style()->isDisplayInlineType());
375     setRelPositioned(style()->position() == RelativePosition);
376     setHorizontalWritingMode(style()->isHorizontalWritingMode());
377 }
378 
relativePositionOffsetX() const379 int RenderBoxModelObject::relativePositionOffsetX() const
380 {
381     // Objects that shrink to avoid floats normally use available line width when computing containing block width.  However
382     // in the case of relative positioning using percentages, we can't do this.  The offset should always be resolved using the
383     // available width of the containing block.  Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly
384     // call availableWidth on our containing block.
385     if (!style()->left().isAuto()) {
386         RenderBlock* cb = containingBlock();
387         if (!style()->right().isAuto() && !cb->style()->isLeftToRightDirection())
388             return -style()->right().calcValue(cb->availableWidth());
389         return style()->left().calcValue(cb->availableWidth());
390     }
391     if (!style()->right().isAuto()) {
392         RenderBlock* cb = containingBlock();
393         return -style()->right().calcValue(cb->availableWidth());
394     }
395     return 0;
396 }
397 
relativePositionOffsetY() const398 int RenderBoxModelObject::relativePositionOffsetY() const
399 {
400     RenderBlock* containingBlock = this->containingBlock();
401 
402     // If the containing block of a relatively positioned element does not
403     // specify a height, a percentage top or bottom offset should be resolved as
404     // auto. An exception to this is if the containing block has the WinIE quirk
405     // where <html> and <body> assume the size of the viewport. In this case,
406     // calculate the percent offset based on this height.
407     // See <https://bugs.webkit.org/show_bug.cgi?id=26396>.
408     if (!style()->top().isAuto()
409         && (!containingBlock->style()->height().isAuto()
410             || !style()->top().isPercent()
411             || containingBlock->stretchesToViewport()))
412         return style()->top().calcValue(containingBlock->availableHeight());
413 
414     if (!style()->bottom().isAuto()
415         && (!containingBlock->style()->height().isAuto()
416             || !style()->bottom().isPercent()
417             || containingBlock->stretchesToViewport()))
418         return -style()->bottom().calcValue(containingBlock->availableHeight());
419 
420     return 0;
421 }
422 
offsetLeft() const423 int RenderBoxModelObject::offsetLeft() const
424 {
425     // If the element is the HTML body element or does not have an associated box
426     // return 0 and stop this algorithm.
427     if (isBody())
428         return 0;
429 
430     RenderBoxModelObject* offsetPar = offsetParent();
431     int xPos = (isBox() ? toRenderBox(this)->x() : 0);
432 
433     // If the offsetParent of the element is null, or is the HTML body element,
434     // return the distance between the canvas origin and the left border edge
435     // of the element and stop this algorithm.
436     if (offsetPar) {
437         if (offsetPar->isBox() && !offsetPar->isBody())
438             xPos -= toRenderBox(offsetPar)->borderLeft();
439         if (!isPositioned()) {
440             if (isRelPositioned())
441                 xPos += relativePositionOffsetX();
442             RenderObject* curr = parent();
443             while (curr && curr != offsetPar) {
444                 // FIXME: What are we supposed to do inside SVG content?
445                 if (curr->isBox() && !curr->isTableRow())
446                     xPos += toRenderBox(curr)->x();
447                 curr = curr->parent();
448             }
449             if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
450                 xPos += toRenderBox(offsetPar)->x();
451         }
452     }
453 
454     return xPos;
455 }
456 
offsetTop() const457 int RenderBoxModelObject::offsetTop() const
458 {
459     // If the element is the HTML body element or does not have an associated box
460     // return 0 and stop this algorithm.
461     if (isBody())
462         return 0;
463 
464     RenderBoxModelObject* offsetPar = offsetParent();
465     int yPos = (isBox() ? toRenderBox(this)->y() : 0);
466 
467     // If the offsetParent of the element is null, or is the HTML body element,
468     // return the distance between the canvas origin and the top border edge
469     // of the element and stop this algorithm.
470     if (offsetPar) {
471         if (offsetPar->isBox() && !offsetPar->isBody())
472             yPos -= toRenderBox(offsetPar)->borderTop();
473         if (!isPositioned()) {
474             if (isRelPositioned())
475                 yPos += relativePositionOffsetY();
476             RenderObject* curr = parent();
477             while (curr && curr != offsetPar) {
478                 // FIXME: What are we supposed to do inside SVG content?
479                 if (curr->isBox() && !curr->isTableRow())
480                     yPos += toRenderBox(curr)->y();
481                 curr = curr->parent();
482             }
483             if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
484                 yPos += toRenderBox(offsetPar)->y();
485         }
486     }
487     return yPos;
488 }
489 
paddingTop(bool) const490 int RenderBoxModelObject::paddingTop(bool) const
491 {
492     int w = 0;
493     Length padding = style()->paddingTop();
494     if (padding.isPercent())
495         w = containingBlock()->availableLogicalWidth();
496     return padding.calcMinValue(w);
497 }
498 
paddingBottom(bool) const499 int RenderBoxModelObject::paddingBottom(bool) const
500 {
501     int w = 0;
502     Length padding = style()->paddingBottom();
503     if (padding.isPercent())
504         w = containingBlock()->availableLogicalWidth();
505     return padding.calcMinValue(w);
506 }
507 
paddingLeft(bool) const508 int RenderBoxModelObject::paddingLeft(bool) const
509 {
510     int w = 0;
511     Length padding = style()->paddingLeft();
512     if (padding.isPercent())
513         w = containingBlock()->availableLogicalWidth();
514     return padding.calcMinValue(w);
515 }
516 
paddingRight(bool) const517 int RenderBoxModelObject::paddingRight(bool) const
518 {
519     int w = 0;
520     Length padding = style()->paddingRight();
521     if (padding.isPercent())
522         w = containingBlock()->availableLogicalWidth();
523     return padding.calcMinValue(w);
524 }
525 
paddingBefore(bool) const526 int RenderBoxModelObject::paddingBefore(bool) const
527 {
528     int w = 0;
529     Length padding = style()->paddingBefore();
530     if (padding.isPercent())
531         w = containingBlock()->availableLogicalWidth();
532     return padding.calcMinValue(w);
533 }
534 
paddingAfter(bool) const535 int RenderBoxModelObject::paddingAfter(bool) const
536 {
537     int w = 0;
538     Length padding = style()->paddingAfter();
539     if (padding.isPercent())
540         w = containingBlock()->availableLogicalWidth();
541     return padding.calcMinValue(w);
542 }
543 
paddingStart(bool) const544 int RenderBoxModelObject::paddingStart(bool) const
545 {
546     int w = 0;
547     Length padding = style()->paddingStart();
548     if (padding.isPercent())
549         w = containingBlock()->availableLogicalWidth();
550     return padding.calcMinValue(w);
551 }
552 
paddingEnd(bool) const553 int RenderBoxModelObject::paddingEnd(bool) const
554 {
555     int w = 0;
556     Length padding = style()->paddingEnd();
557     if (padding.isPercent())
558         w = containingBlock()->availableLogicalWidth();
559     return padding.calcMinValue(w);
560 }
561 
getBackgroundRoundedRect(const IntRect & borderRect,InlineFlowBox * box,int inlineBoxWidth,int inlineBoxHeight,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)562 RoundedIntRect RenderBoxModelObject::getBackgroundRoundedRect(const IntRect& borderRect, InlineFlowBox* box, int inlineBoxWidth, int inlineBoxHeight,
563     bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
564 {
565     RoundedIntRect border = style()->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
566     if (box && (box->nextLineBox() || box->prevLineBox())) {
567         RoundedIntRect segmentBorder = style()->getRoundedBorderFor(IntRect(0, 0, inlineBoxWidth, inlineBoxHeight), includeLogicalLeftEdge, includeLogicalRightEdge);
568         border.setRadii(segmentBorder.radii());
569     }
570 
571     return border;
572 }
573 
backgroundRectAdjustedForBleedAvoidance(GraphicsContext * context,const IntRect & borderRect,BackgroundBleedAvoidance bleedAvoidance)574 static IntRect backgroundRectAdjustedForBleedAvoidance(GraphicsContext* context, const IntRect& borderRect, BackgroundBleedAvoidance bleedAvoidance)
575 {
576     if (bleedAvoidance != BackgroundBleedShrinkBackground)
577         return borderRect;
578 
579     IntRect adjustedRect = borderRect;
580     // We need to shrink the border by one device pixel on each side.
581     AffineTransform ctm = context->getCTM();
582     FloatSize contextScale(static_cast<float>(ctm.xScale()), static_cast<float>(ctm.yScale()));
583     adjustedRect.inflateX(-ceilf(1 / contextScale.width()));
584     adjustedRect.inflateY(-ceilf(1 / contextScale.height()));
585     return adjustedRect;
586 }
587 
paintFillLayerExtended(const PaintInfo & paintInfo,const Color & color,const FillLayer * bgLayer,int tx,int ty,int w,int h,BackgroundBleedAvoidance bleedAvoidance,InlineFlowBox * box,int inlineBoxWidth,int inlineBoxHeight,CompositeOperator op,RenderObject * backgroundObject)588 void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer* bgLayer, int tx, int ty, int w, int h,
589     BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, int inlineBoxWidth, int inlineBoxHeight, CompositeOperator op, RenderObject* backgroundObject)
590 {
591     GraphicsContext* context = paintInfo.context;
592     if (context->paintingDisabled())
593         return;
594 
595     IntRect borderRect(tx, ty, w, h);
596     if (borderRect.isEmpty())
597         return;
598 
599     bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
600     bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
601 
602     bool hasRoundedBorder = style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge);
603     bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment;
604     bool isBorderFill = bgLayer->clip() == BorderFillBox;
605     bool isRoot = this->isRoot();
606 
607     Color bgColor = color;
608     StyleImage* bgImage = bgLayer->image();
609     bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(style()->effectiveZoom());
610 
611     // When this style flag is set, change existing background colors and images to a solid white background.
612     // If there's no bg color or image, leave it untouched to avoid affecting transparency.
613     // We don't try to avoid loading the background images, because this style flag is only set
614     // when printing, and at that point we've already loaded the background images anyway. (To avoid
615     // loading the background images we'd have to do this check when applying styles rather than
616     // while rendering.)
617     if (style()->forceBackgroundsToWhite()) {
618         // Note that we can't reuse this variable below because the bgColor might be changed
619         bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0;
620         if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
621             bgColor = Color::white;
622             shouldPaintBackgroundImage = false;
623         }
624     }
625 
626     bool colorVisible = bgColor.isValid() && bgColor.alpha() > 0;
627 
628     // Fast path for drawing simple color backgrounds.
629     if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill) {
630         if (!colorVisible)
631             return;
632 
633         if (hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer) {
634             RoundedIntRect border = getBackgroundRoundedRect(backgroundRectAdjustedForBleedAvoidance(context, borderRect, bleedAvoidance), box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
635             context->fillRoundedRect(border, bgColor, style()->colorSpace());
636         } else
637             context->fillRect(borderRect, bgColor, style()->colorSpace());
638 
639         return;
640     }
641 
642     bool clipToBorderRadius = hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer;
643     GraphicsContextStateSaver clipToBorderStateSaver(*context, clipToBorderRadius);
644     if (clipToBorderRadius) {
645         RoundedIntRect border = getBackgroundRoundedRect(backgroundRectAdjustedForBleedAvoidance(context, borderRect, bleedAvoidance), box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
646         context->addRoundedRectClip(border);
647     }
648 
649     int bLeft = includeLeftEdge ? borderLeft() : 0;
650     int bRight = includeRightEdge ? borderRight() : 0;
651     int pLeft = includeLeftEdge ? paddingLeft() : 0;
652     int pRight = includeRightEdge ? paddingRight() : 0;
653 
654     GraphicsContextStateSaver clipWithScrollingStateSaver(*context, clippedWithLocalScrolling);
655     if (clippedWithLocalScrolling) {
656         // Clip to the overflow area.
657         context->clip(toRenderBox(this)->overflowClipRect(tx, ty));
658 
659         // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends.
660         IntSize offset = layer()->scrolledContentOffset();
661         tx -= offset.width();
662         ty -= offset.height();
663         w = bLeft + layer()->scrollWidth() + bRight;
664         h = borderTop() + layer()->scrollHeight() + borderBottom();
665     }
666 
667     GraphicsContextStateSaver backgroundClipStateSaver(*context, false);
668     if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) {
669         // Clip to the padding or content boxes as necessary.
670         bool includePadding = bgLayer->clip() == ContentFillBox;
671         int x = tx + bLeft + (includePadding ? pLeft : 0);
672         int y = ty + borderTop() + (includePadding ? paddingTop() : 0);
673         int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0);
674         int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0);
675         backgroundClipStateSaver.save();
676         context->clip(IntRect(x, y, width, height));
677     } else if (bgLayer->clip() == TextFillBox) {
678         // We have to draw our text into a mask that can then be used to clip background drawing.
679         // First figure out how big the mask has to be.  It should be no bigger than what we need
680         // to actually render, so we should intersect the dirty rect with the border box of the background.
681         IntRect maskRect(tx, ty, w, h);
682         maskRect.intersect(paintInfo.rect);
683 
684         // Now create the mask.
685         OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size());
686         if (!maskImage)
687             return;
688 
689         GraphicsContext* maskImageContext = maskImage->context();
690         maskImageContext->translate(-maskRect.x(), -maskRect.y());
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, PaintPhaseTextClip, true, 0, 0);
695         if (box) {
696             RootInlineBox* root = box->root();
697             box->paint(info, tx - box->x(), ty - box->y(), root->lineTop(), root->lineBottom());
698         } else {
699             int x = isBox() ? toRenderBox(this)->x() : 0;
700             int y = isBox() ? toRenderBox(this)->y() : 0;
701             paint(info, tx - x, ty - y);
702         }
703 
704         // The mask has been created.  Now we just need to clip to it.
705         backgroundClipStateSaver.save();
706         context->clipToImageBuffer(maskImage.get(), maskRect);
707     }
708 
709     // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with
710     // no background in the child document should show the parent's background.
711     bool isOpaqueRoot = false;
712     if (isRoot) {
713         isOpaqueRoot = true;
714         if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) {
715             Element* ownerElement = document()->ownerElement();
716             if (ownerElement) {
717                 if (!ownerElement->hasTagName(frameTag)) {
718                     // Locate the <body> element using the DOM.  This is easier than trying
719                     // to crawl around a render tree with potential :before/:after content and
720                     // anonymous blocks created by inline <body> tags etc.  We can locate the <body>
721                     // render object very easily via the DOM.
722                     HTMLElement* body = document()->body();
723                     if (body) {
724                         // Can't scroll a frameset document anyway.
725                         isOpaqueRoot = body->hasLocalName(framesetTag);
726                     }
727 #if ENABLE(SVG)
728                     else {
729                         // SVG documents and XML documents with SVG root nodes are transparent.
730                         isOpaqueRoot = !document()->hasSVGRootNode();
731                     }
732 #endif
733                 }
734             } else
735                 isOpaqueRoot = !view()->frameView()->isTransparent();
736         }
737         view()->frameView()->setContentIsOpaque(isOpaqueRoot);
738     }
739 
740     // Paint the color first underneath all images.
741     if (!bgLayer->next()) {
742         IntRect rect(tx, ty, w, h);
743         rect.intersect(paintInfo.rect);
744         // If we have an alpha and we are painting the root element, go ahead and blend with the base background color.
745         if (isOpaqueRoot) {
746             Color baseColor = view()->frameView()->baseBackgroundColor();
747             if (baseColor.alpha() > 0) {
748                 CompositeOperator previousOperator = context->compositeOperation();
749                 context->setCompositeOperation(CompositeCopy);
750                 context->fillRect(rect, baseColor, style()->colorSpace());
751                 context->setCompositeOperation(previousOperator);
752             } else
753                 context->clearRect(rect);
754         }
755 
756         if (bgColor.isValid() && bgColor.alpha() > 0)
757             context->fillRect(rect, bgColor, style()->colorSpace());
758     }
759 
760     // no progressive loading of the background image
761     if (shouldPaintBackgroundImage) {
762         IntRect destRect;
763         IntPoint phase;
764         IntSize tileSize;
765 
766         calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize);
767         IntPoint destOrigin = destRect.location();
768         destRect.intersect(paintInfo.rect);
769         if (!destRect.isEmpty()) {
770             phase += destRect.location() - destOrigin;
771             CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op;
772             RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this;
773             RefPtr<Image> image = bgImage->image(clientForBackgroundImage, tileSize);
774             bool useLowQualityScaling = shouldPaintAtLowQuality(context, image.get(), bgLayer, tileSize);
775             context->drawTiledImage(image.get(), style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling);
776         }
777     }
778 }
779 
calculateFillTileSize(const FillLayer * fillLayer,IntSize positioningAreaSize) const780 IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const
781 {
782     StyleImage* image = fillLayer->image();
783     image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin.
784 
785     EFillSizeType type = fillLayer->size().type;
786 
787     switch (type) {
788         case SizeLength: {
789             int w = positioningAreaSize.width();
790             int h = positioningAreaSize.height();
791 
792             Length layerWidth = fillLayer->size().size.width();
793             Length layerHeight = fillLayer->size().size.height();
794 
795             if (layerWidth.isFixed())
796                 w = layerWidth.value();
797             else if (layerWidth.isPercent())
798                 w = layerWidth.calcValue(positioningAreaSize.width());
799 
800             if (layerHeight.isFixed())
801                 h = layerHeight.value();
802             else if (layerHeight.isPercent())
803                 h = layerHeight.calcValue(positioningAreaSize.height());
804 
805             // If one of the values is auto we have to use the appropriate
806             // scale to maintain our aspect ratio.
807             if (layerWidth.isAuto() && !layerHeight.isAuto()) {
808                 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
809                 if (imageIntrinsicSize.height())
810                     w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height();
811             } else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
812                 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
813                 if (imageIntrinsicSize.width())
814                     h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width();
815             } else if (layerWidth.isAuto() && layerHeight.isAuto()) {
816                 // If both width and height are auto, use the image's intrinsic size.
817                 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
818                 w = imageIntrinsicSize.width();
819                 h = imageIntrinsicSize.height();
820             }
821 
822             return IntSize(max(1, w), max(1, h));
823         }
824         case Contain:
825         case Cover: {
826             IntSize imageIntrinsicSize = image->imageSize(this, 1);
827             float horizontalScaleFactor = imageIntrinsicSize.width()
828                 ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1;
829             float verticalScaleFactor = imageIntrinsicSize.height()
830                 ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1;
831             float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor);
832             return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor));
833         }
834         case SizeNone:
835             break;
836     }
837 
838     return image->imageSize(this, style()->effectiveZoom());
839 }
840 
calculateBackgroundImageGeometry(const FillLayer * fillLayer,int tx,int ty,int w,int h,IntRect & destRect,IntPoint & phase,IntSize & tileSize)841 void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h,
842                                                             IntRect& destRect, IntPoint& phase, IntSize& tileSize)
843 {
844     int left = 0;
845     int top = 0;
846     IntSize positioningAreaSize;
847 
848     // Determine the background positioning area and set destRect to the background painting area.
849     // destRect will be adjusted later if the background is non-repeating.
850     bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment;
851 
852 #if ENABLE(FAST_MOBILE_SCROLLING)
853     if (view()->frameView() && view()->frameView()->canBlitOnScroll()) {
854         // As a side effect of an optimization to blit on scroll, we do not honor the CSS
855         // property "background-attachment: fixed" because it may result in rendering
856         // artifacts. Note, these artifacts only appear if we are blitting on scroll of
857         // a page that has fixed background images.
858         fixedAttachment = false;
859     }
860 #endif
861 
862     if (!fixedAttachment) {
863         destRect = IntRect(tx, ty, w, h);
864 
865         int right = 0;
866         int bottom = 0;
867         // Scroll and Local.
868         if (fillLayer->origin() != BorderFillBox) {
869             left = borderLeft();
870             right = borderRight();
871             top = borderTop();
872             bottom = borderBottom();
873             if (fillLayer->origin() == ContentFillBox) {
874                 left += paddingLeft();
875                 right += paddingRight();
876                 top += paddingTop();
877                 bottom += paddingBottom();
878             }
879         }
880 
881         // The background of the box generated by the root element covers the entire canvas including
882         // its margins. Since those were added in already, we have to factor them out when computing
883         // the background positioning area.
884         if (isRoot()) {
885             positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom);
886             left += marginLeft();
887             top += marginTop();
888         } else
889             positioningAreaSize = IntSize(w - left - right, h - top - bottom);
890     } else {
891         destRect = viewRect();
892         positioningAreaSize = destRect.size();
893     }
894 
895     tileSize = calculateFillTileSize(fillLayer, positioningAreaSize);
896 
897     EFillRepeat backgroundRepeatX = fillLayer->repeatX();
898     EFillRepeat backgroundRepeatY = fillLayer->repeatY();
899 
900     int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true);
901     if (backgroundRepeatX == RepeatFill)
902         phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0);
903     else {
904         destRect.move(max(xPosition + left, 0), 0);
905         phase.setX(-min(xPosition + left, 0));
906         destRect.setWidth(tileSize.width() + min(xPosition + left, 0));
907     }
908 
909     int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true);
910     if (backgroundRepeatY == RepeatFill)
911         phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0);
912     else {
913         destRect.move(0, max(yPosition + top, 0));
914         phase.setY(-min(yPosition + top, 0));
915         destRect.setHeight(tileSize.height() + min(yPosition + top, 0));
916     }
917 
918     if (fixedAttachment)
919         phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0));
920 
921     destRect.intersect(IntRect(tx, ty, w, h));
922 }
923 
paintNinePieceImage(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,const NinePieceImage & ninePieceImage,CompositeOperator op)924 bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style,
925                                                const NinePieceImage& ninePieceImage, CompositeOperator op)
926 {
927     StyleImage* styleImage = ninePieceImage.image();
928     if (!styleImage)
929         return false;
930 
931     if (!styleImage->isLoaded())
932         return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either.
933 
934     if (!styleImage->canRender(style->effectiveZoom()))
935         return false;
936 
937     // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function
938     // doesn't have any understanding of the zoom that is in effect on the tile.
939     styleImage->setImageContainerSize(IntSize(w, h));
940     IntSize imageSize = styleImage->imageSize(this, 1.0f);
941     int imageWidth = imageSize.width();
942     int imageHeight = imageSize.height();
943 
944     int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight));
945     int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight));
946     int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth));
947     int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth));
948 
949     ENinePieceImageRule hRule = ninePieceImage.horizontalRule();
950     ENinePieceImageRule vRule = ninePieceImage.verticalRule();
951 
952     bool fitToBorder = style->borderImage() == ninePieceImage;
953 
954     int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice;
955     int topWidth = fitToBorder ? style->borderTopWidth() : topSlice;
956     int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice;
957     int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice;
958 
959     bool drawLeft = leftSlice > 0 && leftWidth > 0;
960     bool drawTop = topSlice > 0 && topWidth > 0;
961     bool drawRight = rightSlice > 0 && rightWidth > 0;
962     bool drawBottom = bottomSlice > 0 && bottomWidth > 0;
963     bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 &&
964                       (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0;
965 
966     RefPtr<Image> image = styleImage->image(this, imageSize);
967     ColorSpace colorSpace = style->colorSpace();
968 
969     if (drawLeft) {
970         // Paint the top and bottom left corners.
971 
972         // The top left corner rect is (tx, ty, leftWidth, topWidth)
973         // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice)
974         if (drawTop)
975             graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty, leftWidth, topWidth),
976                                        IntRect(0, 0, leftSlice, topSlice), op);
977 
978         // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth)
979         // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice)
980         if (drawBottom)
981             graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth),
982                                        IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op);
983 
984         // Paint the left edge.
985         // Have to scale and tile into the border rect.
986         graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx, ty + topWidth, leftWidth,
987                                         h - topWidth - bottomWidth),
988                                         IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice),
989                                         Image::StretchTile, (Image::TileRule)vRule, op);
990     }
991 
992     if (drawRight) {
993         // Paint the top and bottom right corners
994         // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth)
995         // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice)
996         if (drawTop)
997             graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth),
998                                        IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op);
999 
1000         // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth)
1001         // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice)
1002         if (drawBottom)
1003             graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth),
1004                                        IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op);
1005 
1006         // Paint the right edge.
1007         graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth,
1008                                         h - topWidth - bottomWidth),
1009                                         IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice),
1010                                         Image::StretchTile, (Image::TileRule)vRule, op);
1011     }
1012 
1013     // Paint the top edge.
1014     if (drawTop)
1015         graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth),
1016                                         IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice),
1017                                         (Image::TileRule)hRule, Image::StretchTile, op);
1018 
1019     // Paint the bottom edge.
1020     if (drawBottom)
1021         graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth,
1022                                         w - leftWidth - rightWidth, bottomWidth),
1023                                         IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice),
1024                                         (Image::TileRule)hRule, Image::StretchTile, op);
1025 
1026     // Paint the middle.
1027     if (drawMiddle)
1028         graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth,
1029                                         h - topWidth - bottomWidth),
1030                                         IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice),
1031                                         (Image::TileRule)hRule, (Image::TileRule)vRule, op);
1032 
1033     return true;
1034 }
1035 
1036 class BorderEdge {
1037 public:
BorderEdge(int edgeWidth,const Color & edgeColor,EBorderStyle edgeStyle,bool edgeIsTransparent,bool edgeIsPresent=true)1038     BorderEdge(int edgeWidth, const Color& edgeColor, EBorderStyle edgeStyle, bool edgeIsTransparent, bool edgeIsPresent = true)
1039         : width(edgeWidth)
1040         , color(edgeColor)
1041         , style(edgeStyle)
1042         , isTransparent(edgeIsTransparent)
1043         , isPresent(edgeIsPresent)
1044     {
1045         if (style == DOUBLE && edgeWidth < 3)
1046             style = SOLID;
1047     }
1048 
BorderEdge()1049     BorderEdge()
1050         : width(0)
1051         , style(BHIDDEN)
1052         , isTransparent(false)
1053         , isPresent(false)
1054     {
1055     }
1056 
hasVisibleColorAndStyle() const1057     bool hasVisibleColorAndStyle() const { return style > BHIDDEN && !isTransparent; }
shouldRender() const1058     bool shouldRender() const { return isPresent && hasVisibleColorAndStyle(); }
presentButInvisible() const1059     bool presentButInvisible() const { return usedWidth() && !hasVisibleColorAndStyle(); }
obscuresBackgroundEdge(float scale) const1060     bool obscuresBackgroundEdge(float scale) const
1061     {
1062         if (!isPresent || isTransparent || width < (2 * scale) || color.hasAlpha() || style == BHIDDEN)
1063             return false;
1064 
1065         if (style == DOTTED || style == DASHED)
1066             return false;
1067 
1068         if (style == DOUBLE)
1069             return width >= 5 * scale; // The outer band needs to be >= 2px wide at unit scale.
1070 
1071         return true;
1072     }
1073 
usedWidth() const1074     int usedWidth() const { return isPresent ? width : 0; }
1075 
getDoubleBorderStripeWidths(int & outerWidth,int & innerWidth) const1076     void getDoubleBorderStripeWidths(int& outerWidth, int& innerWidth) const
1077     {
1078         int fullWidth = usedWidth();
1079         outerWidth = fullWidth / 3;
1080         innerWidth = fullWidth * 2 / 3;
1081 
1082         // We need certain integer rounding results
1083         if (fullWidth % 3 == 2)
1084             outerWidth += 1;
1085 
1086         if (fullWidth % 3 == 1)
1087             innerWidth += 1;
1088     }
1089 
1090     int width;
1091     Color color;
1092     EBorderStyle style;
1093     bool isTransparent;
1094     bool isPresent;
1095 };
1096 
1097 #if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING)
borderWillArcInnerEdge(const IntSize & firstRadius,const IntSize & secondRadius)1098 static bool borderWillArcInnerEdge(const IntSize& firstRadius, const IntSize& secondRadius)
1099 {
1100     return !firstRadius.isZero() || !secondRadius.isZero();
1101 }
1102 
1103 enum BorderEdgeFlag {
1104     TopBorderEdge = 1 << BSTop,
1105     RightBorderEdge = 1 << BSRight,
1106     BottomBorderEdge = 1 << BSBottom,
1107     LeftBorderEdge = 1 << BSLeft,
1108     AllBorderEdges = TopBorderEdge | BottomBorderEdge | LeftBorderEdge | RightBorderEdge
1109 };
1110 
edgeFlagForSide(BoxSide side)1111 static inline BorderEdgeFlag edgeFlagForSide(BoxSide side)
1112 {
1113     return static_cast<BorderEdgeFlag>(1 << side);
1114 }
1115 
includesEdge(BorderEdgeFlags flags,BoxSide side)1116 static inline bool includesEdge(BorderEdgeFlags flags, BoxSide side)
1117 {
1118     return flags & edgeFlagForSide(side);
1119 }
1120 
edgesShareColor(const BorderEdge & firstEdge,const BorderEdge & secondEdge)1121 inline bool edgesShareColor(const BorderEdge& firstEdge, const BorderEdge& secondEdge)
1122 {
1123     return firstEdge.color == secondEdge.color;
1124 }
1125 
styleRequiresClipPolygon(EBorderStyle style)1126 inline bool styleRequiresClipPolygon(EBorderStyle style)
1127 {
1128     return style == DOTTED || style == DASHED; // These are drawn with a stroke, so we have to clip to get corner miters.
1129 }
1130 
borderStyleFillsBorderArea(EBorderStyle style)1131 static bool borderStyleFillsBorderArea(EBorderStyle style)
1132 {
1133     return !(style == DOTTED || style == DASHED || style == DOUBLE);
1134 }
1135 
borderStyleHasInnerDetail(EBorderStyle style)1136 static bool borderStyleHasInnerDetail(EBorderStyle style)
1137 {
1138     return style == GROOVE || style == RIDGE || style == DOUBLE;
1139 }
1140 
borderStyleIsDottedOrDashed(EBorderStyle style)1141 static bool borderStyleIsDottedOrDashed(EBorderStyle style)
1142 {
1143     return style == DOTTED || style == DASHED;
1144 }
1145 
1146 // OUTSET darkens the bottom and right (and maybe lightens the top and left)
1147 // INSET darkens the top and left (and maybe lightens the bottom and right)
borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style,BoxSide side,BoxSide adjacentSide)1148 static inline bool borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style, BoxSide side, BoxSide adjacentSide)
1149 {
1150     // These styles match at the top/left and bottom/right.
1151     if (style == INSET || style == GROOVE || style == RIDGE || style == OUTSET) {
1152         const BorderEdgeFlags topRightFlags = edgeFlagForSide(BSTop) | edgeFlagForSide(BSRight);
1153         const BorderEdgeFlags bottomLeftFlags = edgeFlagForSide(BSBottom) | edgeFlagForSide(BSLeft);
1154 
1155         BorderEdgeFlags flags = edgeFlagForSide(side) | edgeFlagForSide(adjacentSide);
1156         return flags == topRightFlags || flags == bottomLeftFlags;
1157     }
1158     return false;
1159 }
1160 
colorsMatchAtCorner(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1161 static inline bool colorsMatchAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1162 {
1163     if (edges[side].shouldRender() != edges[adjacentSide].shouldRender())
1164         return false;
1165 
1166     if (!edgesShareColor(edges[side], edges[adjacentSide]))
1167         return false;
1168 
1169     return !borderStyleHasUnmatchedColorsAtCorner(edges[side].style, side, adjacentSide);
1170 }
1171 
1172 // This assumes that we draw in order: top, bottom, left, right.
willBeOverdrawn(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1173 static inline bool willBeOverdrawn(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1174 {
1175     switch (side) {
1176     case BSTop:
1177     case BSBottom:
1178         if (edges[adjacentSide].presentButInvisible())
1179             return false;
1180 
1181         if (!edgesShareColor(edges[side], edges[adjacentSide]) && edges[adjacentSide].color.hasAlpha())
1182             return false;
1183 
1184         if (!borderStyleFillsBorderArea(edges[adjacentSide].style))
1185             return false;
1186 
1187         return true;
1188 
1189     case BSLeft:
1190     case BSRight:
1191         // These draw last, so are never overdrawn.
1192         return false;
1193     }
1194     return false;
1195 }
1196 
borderStylesRequireMitre(BoxSide side,BoxSide adjacentSide,EBorderStyle style,EBorderStyle adjacentStyle)1197 static inline bool borderStylesRequireMitre(BoxSide side, BoxSide adjacentSide, EBorderStyle style, EBorderStyle adjacentStyle)
1198 {
1199     if (style == DOUBLE || adjacentStyle == DOUBLE || adjacentStyle == GROOVE || adjacentStyle == RIDGE)
1200         return true;
1201 
1202     if (borderStyleIsDottedOrDashed(style) != borderStyleIsDottedOrDashed(adjacentStyle))
1203         return true;
1204 
1205     if (style != adjacentStyle)
1206         return true;
1207 
1208     return borderStyleHasUnmatchedColorsAtCorner(style, side, adjacentSide);
1209 }
1210 
joinRequiresMitre(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[],bool allowOverdraw)1211 static bool joinRequiresMitre(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[], bool allowOverdraw)
1212 {
1213     if ((edges[side].isTransparent && edges[adjacentSide].isTransparent) || !edges[adjacentSide].isPresent)
1214         return false;
1215 
1216     if (allowOverdraw && willBeOverdrawn(side, adjacentSide, edges))
1217         return false;
1218 
1219     if (!edgesShareColor(edges[side], edges[adjacentSide]))
1220         return true;
1221 
1222     if (borderStylesRequireMitre(side, adjacentSide, edges[side].style, edges[adjacentSide].style))
1223         return true;
1224 
1225     return false;
1226 }
1227 
paintOneBorderSide(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const IntRect & sideRect,BoxSide side,BoxSide adjacentSide1,BoxSide adjacentSide2,const BorderEdge edges[],const Path * path,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1228 void RenderBoxModelObject::paintOneBorderSide(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1229     const IntRect& sideRect, BoxSide side, BoxSide adjacentSide1, BoxSide adjacentSide2, const BorderEdge edges[], const Path* path,
1230     BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1231 {
1232     const BorderEdge& edgeToRender = edges[side];
1233     const BorderEdge& adjacentEdge1 = edges[adjacentSide1];
1234     const BorderEdge& adjacentEdge2 = edges[adjacentSide2];
1235 
1236     bool mitreAdjacentSide1 = joinRequiresMitre(side, adjacentSide1, edges, !antialias);
1237     bool mitreAdjacentSide2 = joinRequiresMitre(side, adjacentSide2, edges, !antialias);
1238 
1239     bool adjacentSide1StylesMatch = colorsMatchAtCorner(side, adjacentSide1, edges);
1240     bool adjacentSide2StylesMatch = colorsMatchAtCorner(side, adjacentSide2, edges);
1241 
1242     const Color& colorToPaint = overrideColor ? *overrideColor : edgeToRender.color;
1243 
1244     if (path) {
1245         GraphicsContextStateSaver stateSaver(*graphicsContext);
1246         clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, adjacentSide1StylesMatch, adjacentSide2StylesMatch);
1247         float thickness = max(max(edgeToRender.width, adjacentEdge1.width), adjacentEdge2.width);
1248         drawBoxSideFromPath(graphicsContext, outerBorder.rect(), *path, edges, edgeToRender.width, thickness, side, style,
1249             colorToPaint, edgeToRender.style, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1250     } else {
1251         bool shouldClip = styleRequiresClipPolygon(edgeToRender.style) && (mitreAdjacentSide1 || mitreAdjacentSide2);
1252 
1253         GraphicsContextStateSaver clipStateSaver(*graphicsContext, shouldClip);
1254         if (shouldClip) {
1255             clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, !mitreAdjacentSide1, !mitreAdjacentSide2);
1256             // Since we clipped, no need to draw with a mitre.
1257             mitreAdjacentSide1 = false;
1258             mitreAdjacentSide2 = false;
1259         }
1260 
1261         drawLineForBoxSide(graphicsContext, sideRect.x(), sideRect.y(), sideRect.maxX(), sideRect.maxY(), side, colorToPaint, edgeToRender.style,
1262                 mitreAdjacentSide1 ? adjacentEdge1.width : 0, mitreAdjacentSide2 ? adjacentEdge2.width : 0, antialias);
1263     }
1264 }
1265 
paintBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],BorderEdgeFlags edgeSet,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1266 void RenderBoxModelObject::paintBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1267                                             const BorderEdge edges[], BorderEdgeFlags edgeSet, BackgroundBleedAvoidance bleedAvoidance,
1268                                             bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1269 {
1270     bool renderRadii = outerBorder.isRounded();
1271 
1272     Path roundedPath;
1273     if (renderRadii)
1274         roundedPath.addRoundedRect(outerBorder);
1275 
1276     if (edges[BSTop].shouldRender() && includesEdge(edgeSet, BSTop)) {
1277         IntRect sideRect = outerBorder.rect();
1278         sideRect.setHeight(edges[BSTop].width);
1279 
1280         bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSTop].style) || borderWillArcInnerEdge(innerBorder.radii().topLeft(), innerBorder.radii().topRight()));
1281         paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSTop, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1282     }
1283 
1284     if (edges[BSBottom].shouldRender() && includesEdge(edgeSet, BSBottom)) {
1285         IntRect sideRect = outerBorder.rect();
1286         sideRect.shiftYEdgeTo(sideRect.maxY() - edges[BSBottom].width);
1287 
1288         bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSBottom].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().bottomRight()));
1289         paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSBottom, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1290     }
1291 
1292     if (edges[BSLeft].shouldRender() && includesEdge(edgeSet, BSLeft)) {
1293         IntRect sideRect = outerBorder.rect();
1294         sideRect.setWidth(edges[BSLeft].width);
1295 
1296         bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSLeft].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().topLeft()));
1297         paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSLeft, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1298     }
1299 
1300     if (edges[BSRight].shouldRender() && includesEdge(edgeSet, BSRight)) {
1301         IntRect sideRect = outerBorder.rect();
1302         sideRect.shiftXEdgeTo(sideRect.maxX() - edges[BSRight].width);
1303 
1304         bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSRight].style) || borderWillArcInnerEdge(innerBorder.radii().bottomRight(), innerBorder.radii().topRight()));
1305         paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSRight, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1306     }
1307 }
1308 
paintTranslucentBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias)1309 void RenderBoxModelObject::paintTranslucentBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1310                                                        const BorderEdge edges[], BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias)
1311 {
1312     BorderEdgeFlags edgesToDraw = AllBorderEdges;
1313     while (edgesToDraw) {
1314         // Find undrawn edges sharing a color.
1315         Color commonColor;
1316 
1317         BorderEdgeFlags commonColorEdgeSet = 0;
1318         for (int i = BSTop; i <= BSLeft; ++i) {
1319             BoxSide currSide = static_cast<BoxSide>(i);
1320             if (!includesEdge(edgesToDraw, currSide))
1321                 continue;
1322 
1323             bool includeEdge;
1324             if (!commonColorEdgeSet) {
1325                 commonColor = edges[currSide].color;
1326                 includeEdge = true;
1327             } else
1328                 includeEdge = edges[currSide].color == commonColor;
1329 
1330             if (includeEdge)
1331                 commonColorEdgeSet |= edgeFlagForSide(currSide);
1332         }
1333 
1334         bool useTransparencyLayer = commonColor.hasAlpha();
1335         if (useTransparencyLayer) {
1336             graphicsContext->beginTransparencyLayer(static_cast<float>(commonColor.alpha()) / 255);
1337             commonColor = Color(commonColor.red(), commonColor.green(), commonColor.blue());
1338         }
1339 
1340         paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, commonColorEdgeSet, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, &commonColor);
1341 
1342         if (useTransparencyLayer)
1343             graphicsContext->endTransparencyLayer();
1344 
1345         edgesToDraw &= ~commonColorEdgeSet;
1346     }
1347 }
1348 
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1349 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1350                                        const RenderStyle* style, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1351 {
1352     // border-image is not affected by border-radius.
1353     if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1354         return;
1355 
1356     if (graphicsContext->paintingDisabled())
1357         return;
1358 
1359     BorderEdge edges[4];
1360     getBorderEdgeInfo(edges, includeLogicalLeftEdge, includeLogicalRightEdge);
1361 
1362     IntRect borderRect(tx, ty, w, h);
1363     RoundedIntRect outerBorder = style->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1364     RoundedIntRect innerBorder = style->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1365 
1366     const AffineTransform& currentCTM = graphicsContext->getCTM();
1367     // FIXME: this isn't quite correct. We may want to antialias when scaled by a non-integral value, or when the translation is non-integral.
1368     bool antialias = !currentCTM.isIdentityOrTranslationOrFlipped();
1369 
1370     bool haveAlphaColor = false;
1371     bool haveAllSolidEdges = true;
1372     bool allEdgesVisible = true;
1373     bool allEdgesShareColor = true;
1374     int firstVisibleEdge = -1;
1375 
1376     for (int i = BSTop; i <= BSLeft; ++i) {
1377         const BorderEdge& currEdge = edges[i];
1378         if (currEdge.presentButInvisible()) {
1379             allEdgesVisible = false;
1380             continue;
1381         }
1382 
1383         if (!currEdge.width)
1384             continue;
1385 
1386         if (firstVisibleEdge == -1)
1387             firstVisibleEdge = i;
1388         else if (currEdge.color != edges[firstVisibleEdge].color)
1389             allEdgesShareColor = false;
1390 
1391         if (currEdge.color.hasAlpha())
1392             haveAlphaColor = true;
1393 
1394         if (currEdge.style != SOLID)
1395             haveAllSolidEdges = false;
1396     }
1397 
1398     // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787
1399     if (haveAllSolidEdges && allEdgesVisible && allEdgesShareColor && innerBorder.isRenderable()) {
1400         // Fast path for drawing all solid edges.
1401         if (outerBorder.isRounded() || haveAlphaColor) {
1402             Path path;
1403 
1404             if (outerBorder.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer)
1405                 path.addRoundedRect(outerBorder);
1406             else
1407                 path.addRect(outerBorder.rect());
1408 
1409             if (innerBorder.isRounded())
1410                 path.addRoundedRect(innerBorder);
1411             else
1412                 path.addRect(innerBorder.rect());
1413 
1414             graphicsContext->setFillRule(RULE_EVENODD);
1415             graphicsContext->setFillColor(edges[firstVisibleEdge].color, style->colorSpace());
1416             graphicsContext->fillPath(path);
1417         } else
1418             paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1419 
1420         return;
1421     }
1422 
1423     bool clipToOuterBorder = outerBorder.isRounded();
1424     GraphicsContextStateSaver stateSaver(*graphicsContext, clipToOuterBorder);
1425     if (clipToOuterBorder) {
1426         // Clip to the inner and outer radii rects.
1427         if (bleedAvoidance != BackgroundBleedUseTransparencyLayer)
1428             graphicsContext->addRoundedRectClip(outerBorder);
1429         graphicsContext->clipOutRoundedRect(innerBorder);
1430     }
1431 
1432     if (haveAlphaColor)
1433         paintTranslucentBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1434     else
1435         paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1436 }
1437 
drawBoxSideFromPath(GraphicsContext * graphicsContext,const IntRect & borderRect,const Path & borderPath,const BorderEdge edges[],float thickness,float drawThickness,BoxSide side,const RenderStyle * style,Color color,EBorderStyle borderStyle,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1438 void RenderBoxModelObject::drawBoxSideFromPath(GraphicsContext* graphicsContext, const IntRect& borderRect, const Path& borderPath, const BorderEdge edges[],
1439                                     float thickness, float drawThickness, BoxSide side, const RenderStyle* style,
1440                                     Color color, EBorderStyle borderStyle, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1441 {
1442     if (thickness <= 0)
1443         return;
1444 
1445     if (borderStyle == DOUBLE && thickness < 3)
1446         borderStyle = SOLID;
1447 
1448     switch (borderStyle) {
1449     case BNONE:
1450     case BHIDDEN:
1451         return;
1452     case DOTTED:
1453     case DASHED: {
1454         graphicsContext->setStrokeColor(color, style->colorSpace());
1455 
1456         // The stroke is doubled here because the provided path is the
1457         // outside edge of the border so half the stroke is clipped off.
1458         // The extra multiplier is so that the clipping mask can antialias
1459         // the edges to prevent jaggies.
1460         graphicsContext->setStrokeThickness(drawThickness * 2 * 1.1f);
1461         graphicsContext->setStrokeStyle(borderStyle == DASHED ? DashedStroke : DottedStroke);
1462 
1463         // If the number of dashes that fit in the path is odd and non-integral then we
1464         // will have an awkwardly-sized dash at the end of the path. To try to avoid that
1465         // here, we simply make the whitespace dashes ever so slightly bigger.
1466         // FIXME: This could be even better if we tried to manipulate the dash offset
1467         // and possibly the gapLength to get the corners dash-symmetrical.
1468         float dashLength = thickness * ((borderStyle == DASHED) ? 3.0f : 1.0f);
1469         float gapLength = dashLength;
1470         float numberOfDashes = borderPath.length() / dashLength;
1471         // Don't try to show dashes if we have less than 2 dashes + 2 gaps.
1472         // FIXME: should do this test per side.
1473         if (numberOfDashes >= 4) {
1474             bool evenNumberOfFullDashes = !((int)numberOfDashes % 2);
1475             bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes);
1476             if (!evenNumberOfFullDashes && !integralNumberOfDashes) {
1477                 float numberOfGaps = numberOfDashes / 2;
1478                 gapLength += (dashLength  / numberOfGaps);
1479             }
1480 
1481             DashArray lineDash;
1482             lineDash.append(dashLength);
1483             lineDash.append(gapLength);
1484             graphicsContext->setLineDash(lineDash, dashLength);
1485         }
1486 
1487         // FIXME: stroking the border path causes issues with tight corners:
1488         // https://bugs.webkit.org/show_bug.cgi?id=58711
1489         // Also, to get the best appearance we should stroke a path between the two borders.
1490         graphicsContext->strokePath(borderPath);
1491         return;
1492     }
1493     case DOUBLE: {
1494         // Get the inner border rects for both the outer border line and the inner border line
1495         int outerBorderTopWidth;
1496         int innerBorderTopWidth;
1497         edges[BSTop].getDoubleBorderStripeWidths(outerBorderTopWidth, innerBorderTopWidth);
1498 
1499         int outerBorderRightWidth;
1500         int innerBorderRightWidth;
1501         edges[BSRight].getDoubleBorderStripeWidths(outerBorderRightWidth, innerBorderRightWidth);
1502 
1503         int outerBorderBottomWidth;
1504         int innerBorderBottomWidth;
1505         edges[BSBottom].getDoubleBorderStripeWidths(outerBorderBottomWidth, innerBorderBottomWidth);
1506 
1507         int outerBorderLeftWidth;
1508         int innerBorderLeftWidth;
1509         edges[BSLeft].getDoubleBorderStripeWidths(outerBorderLeftWidth, innerBorderLeftWidth);
1510 
1511         // Draw inner border line
1512         {
1513             GraphicsContextStateSaver stateSaver(*graphicsContext);
1514             RoundedIntRect innerClip = style->getRoundedInnerBorderFor(borderRect,
1515                 innerBorderTopWidth, innerBorderBottomWidth, innerBorderLeftWidth, innerBorderRightWidth,
1516                 includeLogicalLeftEdge, includeLogicalRightEdge);
1517 
1518             graphicsContext->addRoundedRectClip(innerClip);
1519             drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1520         }
1521 
1522         // Draw outer border line
1523         {
1524             GraphicsContextStateSaver stateSaver(*graphicsContext);
1525             IntRect outerRect = borderRect;
1526             if (bleedAvoidance == BackgroundBleedUseTransparencyLayer) {
1527                 outerRect.inflate(1);
1528                 ++outerBorderTopWidth;
1529                 ++outerBorderBottomWidth;
1530                 ++outerBorderLeftWidth;
1531                 ++outerBorderRightWidth;
1532             }
1533 
1534             RoundedIntRect outerClip = style->getRoundedInnerBorderFor(outerRect,
1535                 outerBorderTopWidth, outerBorderBottomWidth, outerBorderLeftWidth, outerBorderRightWidth,
1536                 includeLogicalLeftEdge, includeLogicalRightEdge);
1537             graphicsContext->clipOutRoundedRect(outerClip);
1538             drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1539         }
1540         return;
1541     }
1542     case RIDGE:
1543     case GROOVE:
1544     {
1545         EBorderStyle s1;
1546         EBorderStyle s2;
1547         if (borderStyle == GROOVE) {
1548             s1 = INSET;
1549             s2 = OUTSET;
1550         } else {
1551             s1 = OUTSET;
1552             s2 = INSET;
1553         }
1554 
1555         // Paint full border
1556         drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s1, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1557 
1558         // Paint inner only
1559         GraphicsContextStateSaver stateSaver(*graphicsContext);
1560         int topWidth = edges[BSTop].usedWidth() / 2;
1561         int bottomWidth = edges[BSBottom].usedWidth() / 2;
1562         int leftWidth = edges[BSLeft].usedWidth() / 2;
1563         int rightWidth = edges[BSRight].usedWidth() / 2;
1564 
1565         RoundedIntRect clipRect = style->getRoundedInnerBorderFor(borderRect,
1566             topWidth, bottomWidth, leftWidth, rightWidth,
1567             includeLogicalLeftEdge, includeLogicalRightEdge);
1568 
1569         graphicsContext->addRoundedRectClip(clipRect);
1570         drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s2, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1571         return;
1572     }
1573     case INSET:
1574         if (side == BSTop || side == BSLeft)
1575             color = color.dark();
1576         break;
1577     case OUTSET:
1578         if (side == BSBottom || side == BSRight)
1579             color = color.dark();
1580         break;
1581     default:
1582         break;
1583     }
1584 
1585     graphicsContext->setStrokeStyle(NoStroke);
1586     graphicsContext->setFillColor(color, style->colorSpace());
1587     graphicsContext->drawRect(borderRect);
1588 }
1589 #else
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,BackgroundBleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1590 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1591                                        const RenderStyle* style, BackgroundBleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1592 {
1593     // FIXME: This old version of paintBorder should be removed when all ports implement
1594     // GraphicsContext::clipConvexPolygon()!! This should happen soon.
1595     if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1596         return;
1597 
1598     const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor);
1599     const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor);
1600     const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor);
1601     const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor);
1602 
1603     bool topTransparent = style->borderTopIsTransparent();
1604     bool bottomTransparent = style->borderBottomIsTransparent();
1605     bool rightTransparent = style->borderRightIsTransparent();
1606     bool leftTransparent = style->borderLeftIsTransparent();
1607 
1608     EBorderStyle topStyle = style->borderTopStyle();
1609     EBorderStyle bottomStyle = style->borderBottomStyle();
1610     EBorderStyle leftStyle = style->borderLeftStyle();
1611     EBorderStyle rightStyle = style->borderRightStyle();
1612 
1613     bool horizontal = style->isHorizontalWritingMode();
1614     bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge);
1615     bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge);
1616     bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge);
1617     bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge);
1618 
1619 
1620     RoundedIntRect border(tx, ty, w, h);
1621 
1622     GraphicsContextStateSaver stateSaver(*graphicsContext, false);
1623     if (style->hasBorderRadius()) {
1624         border.includeLogicalEdges(style->getRoundedBorderFor(border.rect()).radii(),
1625                                    horizontal, includeLogicalLeftEdge, includeLogicalRightEdge);
1626         if (border.isRounded()) {
1627             stateSaver.save();
1628             graphicsContext->addRoundedRectClip(border);
1629         }
1630     }
1631 
1632     int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan;
1633     float thickness;
1634     bool renderRadii = border.isRounded();
1635     bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor);
1636     bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE);
1637     bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE);
1638     bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor);
1639 
1640     if (renderTop) {
1641         bool ignoreLeft = (renderRadii && border.radii().topLeft().width() > 0)
1642             || (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET
1643                 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1644 
1645         bool ignoreRight = (renderRadii && border.radii().topRight().width() > 0)
1646             || (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET
1647                 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1648 
1649         int x = tx;
1650         int x2 = tx + w;
1651         if (renderRadii) {
1652             x += border.radii().topLeft().width();
1653             x2 -= border.radii().topRight().width();
1654         }
1655 
1656         drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle,
1657                    ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1658 
1659         if (renderRadii) {
1660             int leftY = ty;
1661 
1662             // We make the arc double thick and let the clip rect take care of clipping the extra off.
1663             // We're doing this because it doesn't seem possible to match the curve of the clip exactly
1664             // with the arc-drawing function.
1665             thickness = style->borderTopWidth() * 2;
1666 
1667             if (border.radii().topLeft().width()) {
1668                 int leftX = tx;
1669                 // The inner clip clips inside the arc. This is especially important for 1px borders.
1670                 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1671                     && (style->borderTopWidth() < border.radii().topLeft().height())
1672                     && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1673 
1674                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyLeftInnerClip);
1675                 if (applyLeftInnerClip)
1676                     graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1677                                                              style->borderTopWidth());
1678 
1679                 firstAngleStart = 90;
1680                 firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45;
1681 
1682                 // Draw upper left arc
1683                 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1684                               BSTop, topColor, topStyle, true);
1685             }
1686 
1687             if (border.radii().topRight().width()) {
1688                 int rightX = tx + w - border.radii().topRight().width() * 2;
1689                 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1690                     && (style->borderTopWidth() < border.radii().topRight().height())
1691                     && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1692 
1693                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyRightInnerClip);
1694                 if (applyRightInnerClip)
1695                     graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1696                                                              style->borderTopWidth());
1697 
1698                 if (upperRightBorderStylesMatch) {
1699                     secondAngleStart = 0;
1700                     secondAngleSpan = 90;
1701                 } else {
1702                     secondAngleStart = 45;
1703                     secondAngleSpan = 45;
1704                 }
1705 
1706                 // Draw upper right arc
1707                 drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, border.radii().topRight(), secondAngleStart, secondAngleSpan,
1708                               BSTop, topColor, topStyle, false);
1709             }
1710         }
1711     }
1712 
1713     if (renderBottom) {
1714         bool ignoreLeft = (renderRadii && border.radii().bottomLeft().width() > 0)
1715             || (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET
1716                 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1717 
1718         bool ignoreRight = (renderRadii && border.radii().bottomRight().width() > 0)
1719             || (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET
1720                 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1721 
1722         int x = tx;
1723         int x2 = tx + w;
1724         if (renderRadii) {
1725             x += border.radii().bottomLeft().width();
1726             x2 -= border.radii().bottomRight().width();
1727         }
1728 
1729         drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, bottomStyle,
1730                    ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1731 
1732         if (renderRadii) {
1733             thickness = style->borderBottomWidth() * 2;
1734 
1735             if (border.radii().bottomLeft().width()) {
1736                 int leftX = tx;
1737                 int leftY = ty + h - border.radii().bottomLeft().height() * 2;
1738                 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1739                     && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1740                     && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1741 
1742                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyLeftInnerClip);
1743                 if (applyLeftInnerClip)
1744                     graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1745                                                              style->borderBottomWidth());
1746 
1747                 if (lowerLeftBorderStylesMatch) {
1748                     firstAngleStart = 180;
1749                     firstAngleSpan = 90;
1750                 } else {
1751                     firstAngleStart = 225;
1752                     firstAngleSpan = 45;
1753                 }
1754 
1755                 // Draw lower left arc
1756                 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().bottomLeft(), firstAngleStart, firstAngleSpan,
1757                               BSBottom, bottomColor, bottomStyle, true);
1758             }
1759 
1760             if (border.radii().bottomRight().width()) {
1761                 int rightY = ty + h - border.radii().bottomRight().height() * 2;
1762                 int rightX = tx + w - border.radii().bottomRight().width() * 2;
1763                 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1764                     && (style->borderBottomWidth() < border.radii().bottomRight().height())
1765                     && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1766 
1767                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyRightInnerClip);
1768                 if (applyRightInnerClip)
1769                     graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1770                                                              style->borderBottomWidth());
1771 
1772                 secondAngleStart = 270;
1773                 secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45;
1774 
1775                 // Draw lower right arc
1776                 drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1777                               BSBottom, bottomColor, bottomStyle, false);
1778             }
1779         }
1780     }
1781 
1782     if (renderLeft) {
1783         bool ignoreTop = (renderRadii && border.radii().topLeft().height() > 0)
1784             || (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET
1785                 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1786 
1787         bool ignoreBottom = (renderRadii && border.radii().bottomLeft().height() > 0)
1788             || (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET
1789                 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1790 
1791         int y = ty;
1792         int y2 = ty + h;
1793         if (renderRadii) {
1794             y += border.radii().topLeft().height();
1795             y2 -= border.radii().bottomLeft().height();
1796         }
1797 
1798         drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, leftStyle,
1799                    ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1800 
1801         if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) {
1802             int topX = tx;
1803             thickness = style->borderLeftWidth() * 2;
1804 
1805             if (!upperLeftBorderStylesMatch && border.radii().topLeft().width()) {
1806                 int topY = ty;
1807                 bool applyTopInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1808                     && (style->borderTopWidth() < border.radii().topLeft().height())
1809                     && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1810 
1811                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyTopInnerClip);
1812                 if (applyTopInnerClip)
1813                     graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1814                                                              style->borderLeftWidth());
1815 
1816                 firstAngleStart = 135;
1817                 firstAngleSpan = 45;
1818 
1819                 // Draw top left arc
1820                 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1821                               BSLeft, leftColor, leftStyle, true);
1822             }
1823 
1824             if (!lowerLeftBorderStylesMatch && border.radii().bottomLeft().width()) {
1825                 int bottomY = ty + h - border.radii().bottomLeft().height() * 2;
1826                 bool applyBottomInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1827                     && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1828                     && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1829 
1830                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyBottomInnerClip);
1831                 if (applyBottomInnerClip)
1832                     graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1833                                                              style->borderLeftWidth());
1834 
1835                 secondAngleStart = 180;
1836                 secondAngleSpan = 45;
1837 
1838                 // Draw bottom left arc
1839                 drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, border.radii().bottomLeft(), secondAngleStart, secondAngleSpan,
1840                               BSLeft, leftColor, leftStyle, false);
1841             }
1842         }
1843     }
1844 
1845     if (renderRight) {
1846         bool ignoreTop = (renderRadii && border.radii().topRight().height() > 0)
1847             || ((topColor == rightColor) && (topTransparent == rightTransparent)
1848                 && (rightStyle >= DOTTED || rightStyle == INSET)
1849                 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1850 
1851         bool ignoreBottom = (renderRadii && border.radii().bottomRight().height() > 0)
1852             || ((bottomColor == rightColor) && (bottomTransparent == rightTransparent)
1853                 && (rightStyle >= DOTTED || rightStyle == INSET)
1854                 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1855 
1856         int y = ty;
1857         int y2 = ty + h;
1858         if (renderRadii) {
1859             y += border.radii().topRight().height();
1860             y2 -= border.radii().bottomRight().height();
1861         }
1862 
1863         drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, rightStyle,
1864                    ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1865 
1866         if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) {
1867             thickness = style->borderRightWidth() * 2;
1868 
1869             if (!upperRightBorderStylesMatch && border.radii().topRight().width()) {
1870                 int topX = tx + w - border.radii().topRight().width() * 2;
1871                 int topY = ty;
1872                 bool applyTopInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1873                     && (style->borderTopWidth() < border.radii().topRight().height())
1874                     && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1875 
1876                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyTopInnerClip);
1877                 if (applyTopInnerClip)
1878                     graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1879                                                              style->borderRightWidth());
1880 
1881                 firstAngleStart = 0;
1882                 firstAngleSpan = 45;
1883 
1884                 // Draw top right arc
1885                 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topRight(), firstAngleStart, firstAngleSpan,
1886                               BSRight, rightColor, rightStyle, true);
1887             }
1888 
1889             if (!lowerRightBorderStylesMatch && border.radii().bottomRight().width()) {
1890                 int bottomX = tx + w - border.radii().bottomRight().width() * 2;
1891                 int bottomY = ty + h - border.radii().bottomRight().height() * 2;
1892                 bool applyBottomInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1893                     && (style->borderBottomWidth() < border.radii().bottomRight().height())
1894                     && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1895 
1896                 GraphicsContextStateSaver stateSaver(*graphicsContext, applyBottomInnerClip);
1897                 if (applyBottomInnerClip)
1898                     graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1899                                                              style->borderRightWidth());
1900 
1901                 secondAngleStart = 315;
1902                 secondAngleSpan = 45;
1903 
1904                 // Draw bottom right arc
1905                 drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1906                               BSRight, rightColor, rightStyle, false);
1907             }
1908         }
1909     }
1910 }
1911 #endif
1912 
findInnerVertex(const FloatPoint & outerCorner,const FloatPoint & innerCorner,const FloatPoint & centerPoint,FloatPoint & result)1913 static void findInnerVertex(const FloatPoint& outerCorner, const FloatPoint& innerCorner, const FloatPoint& centerPoint, FloatPoint& result)
1914 {
1915     // If the line between outer and inner corner is towards the horizontal, intersect with a vertical line through the center,
1916     // otherwise with a horizontal line through the center. The points that form this line are arbitrary (we use 0, 100).
1917     // Note that if findIntersection fails, it will leave result untouched.
1918     if (fabs(outerCorner.x() - innerCorner.x()) > fabs(outerCorner.y() - innerCorner.y()))
1919         findIntersection(outerCorner, innerCorner, FloatPoint(centerPoint.x(), 0), FloatPoint(centerPoint.x(), 100), result);
1920     else
1921         findIntersection(outerCorner, innerCorner, FloatPoint(0, centerPoint.y()), FloatPoint(100, centerPoint.y()), result);
1922 }
1923 
clipBorderSidePolygon(GraphicsContext * graphicsContext,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,BoxSide side,bool firstEdgeMatches,bool secondEdgeMatches)1924 void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1925                                                  BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches)
1926 {
1927     FloatPoint quad[4];
1928 
1929     const IntRect& outerRect = outerBorder.rect();
1930     const IntRect& innerRect = innerBorder.rect();
1931 
1932     FloatPoint centerPoint(innerRect.location().x() + static_cast<float>(innerRect.width()) / 2, innerRect.location().y() + static_cast<float>(innerRect.height()) / 2);
1933 
1934     // For each side, create a quad that encompasses all parts of that side that may draw,
1935     // including areas inside the innerBorder.
1936     //
1937     //         0----------------3
1938     //       0  \              /  0
1939     //       |\  1----------- 2  /|
1940     //       | 1                1 |
1941     //       | |                | |
1942     //       | |                | |
1943     //       | 2                2 |
1944     //       |/  1------------2  \|
1945     //       3  /              \  3
1946     //         0----------------3
1947     //
1948     switch (side) {
1949     case BSTop:
1950         quad[0] = outerRect.minXMinYCorner();
1951         quad[1] = innerRect.minXMinYCorner();
1952         quad[2] = innerRect.maxXMinYCorner();
1953         quad[3] = outerRect.maxXMinYCorner();
1954 
1955         if (!innerBorder.radii().topLeft().isZero())
1956             findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1957 
1958         if (!innerBorder.radii().topRight().isZero())
1959             findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[2]);
1960         break;
1961 
1962     case BSLeft:
1963         quad[0] = outerRect.minXMinYCorner();
1964         quad[1] = innerRect.minXMinYCorner();
1965         quad[2] = innerRect.minXMaxYCorner();
1966         quad[3] = outerRect.minXMaxYCorner();
1967 
1968         if (!innerBorder.radii().topLeft().isZero())
1969             findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1970 
1971         if (!innerBorder.radii().bottomLeft().isZero())
1972             findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[2]);
1973         break;
1974 
1975     case BSBottom:
1976         quad[0] = outerRect.minXMaxYCorner();
1977         quad[1] = innerRect.minXMaxYCorner();
1978         quad[2] = innerRect.maxXMaxYCorner();
1979         quad[3] = outerRect.maxXMaxYCorner();
1980 
1981         if (!innerBorder.radii().bottomLeft().isZero())
1982             findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[1]);
1983 
1984         if (!innerBorder.radii().bottomRight().isZero())
1985             findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
1986         break;
1987 
1988     case BSRight:
1989         quad[0] = outerRect.maxXMinYCorner();
1990         quad[1] = innerRect.maxXMinYCorner();
1991         quad[2] = innerRect.maxXMaxYCorner();
1992         quad[3] = outerRect.maxXMaxYCorner();
1993 
1994         if (!innerBorder.radii().topRight().isZero())
1995             findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[1]);
1996 
1997         if (!innerBorder.radii().bottomRight().isZero())
1998             findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
1999         break;
2000     }
2001 
2002     // If the border matches both of its adjacent sides, don't anti-alias the clip, and
2003     // if neither side matches, anti-alias the clip.
2004     if (firstEdgeMatches == secondEdgeMatches) {
2005         graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches);
2006         return;
2007     }
2008 
2009     // Square off the end which shouldn't be affected by antialiasing, and clip.
2010     FloatPoint firstQuad[4];
2011     firstQuad[0] = quad[0];
2012     firstQuad[1] = quad[1];
2013     firstQuad[2] = side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y())
2014         : FloatPoint(quad[2].x(), quad[3].y());
2015     firstQuad[3] = quad[3];
2016     graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches);
2017 
2018     FloatPoint secondQuad[4];
2019     secondQuad[0] = quad[0];
2020     secondQuad[1] = side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y())
2021         : FloatPoint(quad[1].x(), quad[0].y());
2022     secondQuad[2] = quad[2];
2023     secondQuad[3] = quad[3];
2024     // Antialiasing affects the second side.
2025     graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches);
2026 }
2027 
getBorderEdgeInfo(BorderEdge edges[],bool includeLogicalLeftEdge,bool includeLogicalRightEdge) const2028 void RenderBoxModelObject::getBorderEdgeInfo(BorderEdge edges[], bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const
2029 {
2030     const RenderStyle* style = this->style();
2031     bool horizontal = style->isHorizontalWritingMode();
2032 
2033     edges[BSTop] = BorderEdge(style->borderTopWidth(),
2034         style->visitedDependentColor(CSSPropertyBorderTopColor),
2035         style->borderTopStyle(),
2036         style->borderTopIsTransparent(),
2037         horizontal || includeLogicalLeftEdge);
2038 
2039     edges[BSRight] = BorderEdge(style->borderRightWidth(),
2040         style->visitedDependentColor(CSSPropertyBorderRightColor),
2041         style->borderRightStyle(),
2042         style->borderRightIsTransparent(),
2043         !horizontal || includeLogicalRightEdge);
2044 
2045     edges[BSBottom] = BorderEdge(style->borderBottomWidth(),
2046         style->visitedDependentColor(CSSPropertyBorderBottomColor),
2047         style->borderBottomStyle(),
2048         style->borderBottomIsTransparent(),
2049         horizontal || includeLogicalRightEdge);
2050 
2051     edges[BSLeft] = BorderEdge(style->borderLeftWidth(),
2052         style->visitedDependentColor(CSSPropertyBorderLeftColor),
2053         style->borderLeftStyle(),
2054         style->borderLeftIsTransparent(),
2055         !horizontal || includeLogicalLeftEdge);
2056 }
2057 
borderObscuresBackgroundEdge(const FloatSize & contextScale) const2058 bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const
2059 {
2060     BorderEdge edges[4];
2061     getBorderEdgeInfo(edges);
2062 
2063     for (int i = BSTop; i <= BSLeft; ++i) {
2064         const BorderEdge& currEdge = edges[i];
2065         // FIXME: for vertical text
2066         float axisScale = (i == BSTop || i == BSBottom) ? contextScale.height() : contextScale.width();
2067         if (!currEdge.obscuresBackgroundEdge(axisScale))
2068             return false;
2069     }
2070 
2071     return true;
2072 }
2073 
areaCastingShadowInHole(const IntRect & holeRect,int shadowBlur,int shadowSpread,const IntSize & shadowOffset)2074 static inline IntRect areaCastingShadowInHole(const IntRect& holeRect, int shadowBlur, int shadowSpread, const IntSize& shadowOffset)
2075 {
2076     IntRect bounds(holeRect);
2077 
2078     bounds.inflate(shadowBlur);
2079 
2080     if (shadowSpread < 0)
2081         bounds.inflate(-shadowSpread);
2082 
2083     IntRect offsetBounds = bounds;
2084     offsetBounds.move(-shadowOffset);
2085     return unionRect(bounds, offsetBounds);
2086 }
2087 
paintBoxShadow(GraphicsContext * context,int tx,int ty,int w,int h,const RenderStyle * s,ShadowStyle shadowStyle,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)2088 void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
2089 {
2090     // FIXME: Deal with border-image.  Would be great to use border-image as a mask.
2091 
2092     if (context->paintingDisabled() || !s->boxShadow())
2093         return;
2094 
2095     IntRect borderRect(tx, ty, w, h);
2096     RoundedIntRect border = (shadowStyle == Inset) ? s->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge)
2097                                                    : s->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
2098 
2099     bool hasBorderRadius = s->hasBorderRadius();
2100     bool isHorizontal = s->isHorizontalWritingMode();
2101 
2102     bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255;
2103     for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) {
2104         if (shadow->style() != shadowStyle)
2105             continue;
2106 
2107         IntSize shadowOffset(shadow->x(), shadow->y());
2108         int shadowBlur = shadow->blur();
2109         int shadowSpread = shadow->spread();
2110 
2111         if (shadowOffset.isZero() && !shadowBlur && !shadowSpread)
2112             continue;
2113 
2114         const Color& shadowColor = shadow->color();
2115 
2116         if (shadow->style() == Normal) {
2117             RoundedIntRect fillRect = border;
2118             fillRect.inflate(shadowSpread);
2119             if (fillRect.isEmpty())
2120                 continue;
2121 
2122             IntRect shadowRect(border.rect());
2123             shadowRect.inflate(shadowBlur + shadowSpread);
2124             shadowRect.move(shadowOffset);
2125 
2126             GraphicsContextStateSaver stateSaver(*context);
2127             context->clip(shadowRect);
2128 
2129             // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not
2130             // bleed in (due to antialiasing) if the context is transformed.
2131             IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0);
2132             shadowOffset -= extraOffset;
2133             fillRect.move(extraOffset);
2134 
2135             if (shadow->isWebkitBoxShadow())
2136                 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2137             else
2138                 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2139 
2140             if (hasBorderRadius) {
2141                 RoundedIntRect rectToClipOut = border;
2142 
2143                 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2144                 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2145                 // corners. Those are avoided by insetting the clipping path by one pixel.
2146                 if (hasOpaqueBackground) {
2147                     rectToClipOut.inflateWithRadii(-1);
2148                 }
2149 
2150                 if (!rectToClipOut.isEmpty())
2151                     context->clipOutRoundedRect(rectToClipOut);
2152 
2153                 fillRect.expandRadii(shadowSpread);
2154                 context->fillRoundedRect(fillRect, Color::black, s->colorSpace());
2155             } else {
2156                 IntRect rectToClipOut = border.rect();
2157 
2158                 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2159                 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2160                 // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path
2161                 // by one pixel.
2162                 if (hasOpaqueBackground) {
2163                     AffineTransform currentTransformation = context->getCTM();
2164                     if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1)
2165                             || currentTransformation.b() || currentTransformation.c())
2166                         rectToClipOut.inflate(-1);
2167                 }
2168 
2169                 if (!rectToClipOut.isEmpty())
2170                     context->clipOut(rectToClipOut);
2171                 context->fillRect(fillRect.rect(), Color::black, s->colorSpace());
2172             }
2173         } else {
2174             // Inset shadow.
2175             IntRect holeRect(border.rect());
2176             holeRect.inflate(-shadowSpread);
2177 
2178             if (holeRect.isEmpty()) {
2179                 if (hasBorderRadius)
2180                     context->fillRoundedRect(border, shadowColor, s->colorSpace());
2181                 else
2182                     context->fillRect(border.rect(), shadowColor, s->colorSpace());
2183                 continue;
2184             }
2185 
2186             if (!includeLogicalLeftEdge) {
2187                 if (isHorizontal) {
2188                     holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0);
2189                     holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur);
2190                 } else {
2191                     holeRect.move(0, -max(shadowOffset.height(), 0) - shadowBlur);
2192                     holeRect.setHeight(holeRect.height() + max(shadowOffset.height(), 0) + shadowBlur);
2193                 }
2194             }
2195             if (!includeLogicalRightEdge) {
2196                 if (isHorizontal)
2197                     holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur);
2198                 else
2199                     holeRect.setHeight(holeRect.height() - min(shadowOffset.height(), 0) + shadowBlur);
2200             }
2201 
2202             Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255);
2203 
2204             IntRect outerRect = areaCastingShadowInHole(border.rect(), shadowBlur, shadowSpread, shadowOffset);
2205             RoundedIntRect roundedHole(holeRect, border.radii());
2206 
2207             GraphicsContextStateSaver stateSaver(*context);
2208             if (hasBorderRadius) {
2209                 Path path;
2210                 path.addRoundedRect(border);
2211                 context->clip(path);
2212                 roundedHole.shrinkRadii(shadowSpread);
2213             } else
2214                 context->clip(border.rect());
2215 
2216             IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0);
2217             context->translate(extraOffset.width(), extraOffset.height());
2218             shadowOffset -= extraOffset;
2219 
2220             if (shadow->isWebkitBoxShadow())
2221                 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2222             else
2223                 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2224 
2225             context->fillRectWithRoundedHole(outerRect, roundedHole, fillColor, s->colorSpace());
2226         }
2227     }
2228 }
2229 
containingBlockLogicalWidthForContent() const2230 int RenderBoxModelObject::containingBlockLogicalWidthForContent() const
2231 {
2232     return containingBlock()->availableLogicalWidth();
2233 }
2234 
continuation() const2235 RenderBoxModelObject* RenderBoxModelObject::continuation() const
2236 {
2237     if (!continuationMap)
2238         return 0;
2239     return continuationMap->get(this);
2240 }
2241 
setContinuation(RenderBoxModelObject * continuation)2242 void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation)
2243 {
2244     if (continuation) {
2245         if (!continuationMap)
2246             continuationMap = new ContinuationMap;
2247         continuationMap->set(this, continuation);
2248     } else {
2249         if (continuationMap)
2250             continuationMap->remove(this);
2251     }
2252 }
2253 
2254 } // namespace WebCore
2255