Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: Source/core/paint/BoxPainter.cpp

Issue 1145993002: Refactor root element background painting (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: revise for xianzhu's comment Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "config.h" 5 #include "config.h"
6 #include "core/paint/BoxPainter.h" 6 #include "core/paint/BoxPainter.h"
7 7
8 #include "core/HTMLNames.h" 8 #include "core/HTMLNames.h"
9 #include "core/frame/Settings.h" 9 #include "core/frame/Settings.h"
10 #include "core/html/HTMLFrameOwnerElement.h" 10 #include "core/html/HTMLFrameOwnerElement.h"
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
54 LayoutRect paintRect = m_layoutBox.borderBoxRect(); 54 LayoutRect paintRect = m_layoutBox.borderBoxRect();
55 paintRect.moveBy(paintOffset); 55 paintRect.moveBy(paintOffset);
56 paintBoxDecorationBackgroundWithRect(paintInfo, paintOffset, paintRect); 56 paintBoxDecorationBackgroundWithRect(paintInfo, paintOffset, paintRect);
57 } 57 }
58 58
59 LayoutRect BoxPainter::boundsForDrawingRecorder(const LayoutPoint& paintOffset) 59 LayoutRect BoxPainter::boundsForDrawingRecorder(const LayoutPoint& paintOffset)
60 { 60 {
61 if (!RuntimeEnabledFeatures::slimmingPaintEnabled()) 61 if (!RuntimeEnabledFeatures::slimmingPaintEnabled())
62 return LayoutRect(); 62 return LayoutRect();
63 63
64 // The document element is specified to paint its background infinitely.
65 if (m_layoutBox.isDocumentElement())
66 return rootBackgroundRect();
67
68 // Use the visual overflow rect here, because it will include overflow intro duced by the theme. 64 // Use the visual overflow rect here, because it will include overflow intro duced by the theme.
69 LayoutRect bounds = m_layoutBox.visualOverflowRect(); 65 LayoutRect bounds = m_layoutBox.visualOverflowRect();
70 bounds.moveBy(paintOffset); 66 bounds.moveBy(paintOffset);
71 return LayoutRect(pixelSnappedIntRect(bounds)); 67 return LayoutRect(pixelSnappedIntRect(bounds));
72 } 68 }
73 69
74 LayoutRect BoxPainter::rootBackgroundRect()
75 {
76 LayoutView* layoutView = m_layoutBox.view();
77 LayoutRect result = layoutView->backgroundRect(&m_layoutBox);
78 // In root-layer-scrolls mode, root background is painted in coordinates of the
79 // root scrolling contents layer, so don't need scroll offset adjustment.
80 if (layoutView->hasOverflowClip() && !layoutView->frame()->settings()->rootL ayerScrolls())
81 result.move(-layoutView->scrolledContentOffset());
82 return result;
83 }
84
85 namespace { 70 namespace {
86 71
87 bool bleedAvoidanceIsClipping(BackgroundBleedAvoidance bleedAvoidance) 72 bool bleedAvoidanceIsClipping(BackgroundBleedAvoidance bleedAvoidance)
88 { 73 {
89 return bleedAvoidance == BackgroundBleedClipOnly || bleedAvoidance == Backgr oundBleedClipLayer; 74 return bleedAvoidance == BackgroundBleedClipOnly || bleedAvoidance == Backgr oundBleedClipLayer;
90 } 75 }
91 76
92 } // anonymous namespace 77 } // anonymous namespace
93 78
94 void BoxPainter::paintBoxDecorationBackgroundWithRect(const PaintInfo& paintInfo , const LayoutPoint& paintOffset, const LayoutRect& paintRect) 79 void BoxPainter::paintBoxDecorationBackgroundWithRect(const PaintInfo& paintInfo , const LayoutPoint& paintOffset, const LayoutRect& paintRect)
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
142 // The theme will tell us whether or not we should also paint the CSS border . 127 // The theme will tell us whether or not we should also paint the CSS border .
143 if (boxDecorationData.hasBorder && boxDecorationData.bleedAvoidance != Backg roundBleedBackgroundOverBorder 128 if (boxDecorationData.hasBorder && boxDecorationData.bleedAvoidance != Backg roundBleedBackgroundOverBorder
144 && (!boxDecorationData.hasAppearance || (!themePainted && LayoutTheme::t heme().painter().paintBorderOnly(&m_layoutBox, paintInfo, snappedPaintRect))) 129 && (!boxDecorationData.hasAppearance || (!themePainted && LayoutTheme::t heme().painter().paintBorderOnly(&m_layoutBox, paintInfo, snappedPaintRect)))
145 && !(m_layoutBox.isTable() && toLayoutTable(&m_layoutBox)->collapseBorde rs())) 130 && !(m_layoutBox.isTable() && toLayoutTable(&m_layoutBox)->collapseBorde rs()))
146 paintBorder(m_layoutBox, paintInfo, paintRect, style, boxDecorationData. bleedAvoidance); 131 paintBorder(m_layoutBox, paintInfo, paintRect, style, boxDecorationData. bleedAvoidance);
147 132
148 if (boxDecorationData.bleedAvoidance == BackgroundBleedClipLayer) 133 if (boxDecorationData.bleedAvoidance == BackgroundBleedClipLayer)
149 paintInfo.context->endLayer(); 134 paintInfo.context->endLayer();
150 } 135 }
151 136
152 static bool skipBodyBackground(const LayoutBox* bodyElementLayoutObject)
153 {
154 ASSERT(bodyElementLayoutObject->isBody());
155 // The <body> only paints its background if the root element has defined a b ackground independent of the body,
156 // or if the <body>'s parent is not the document element's layoutObject (e.g . inside SVG foreignObject).
157 LayoutObject* documentElementLayoutObject = bodyElementLayoutObject->documen t().documentElement()->layoutObject();
158 return documentElementLayoutObject
159 && !documentElementLayoutObject->hasBackground()
160 && (documentElementLayoutObject == bodyElementLayoutObject->parent());
161 }
162
163 void BoxPainter::paintBackground(const PaintInfo& paintInfo, const LayoutRect& p aintRect, const Color& backgroundColor, BackgroundBleedAvoidance bleedAvoidance) 137 void BoxPainter::paintBackground(const PaintInfo& paintInfo, const LayoutRect& p aintRect, const Color& backgroundColor, BackgroundBleedAvoidance bleedAvoidance)
164 { 138 {
165 if (m_layoutBox.isDocumentElement()) { 139 if (m_layoutBox.isDocumentElement())
166 paintRootBoxFillLayers(paintInfo);
167 return; 140 return;
168 } 141 if (m_layoutBox.backgroundStolenForBeingBody())
169 if (m_layoutBox.isBody() && skipBodyBackground(&m_layoutBox))
170 return; 142 return;
171 if (m_layoutBox.boxDecorationBackgroundIsKnownToBeObscured()) 143 if (m_layoutBox.boxDecorationBackgroundIsKnownToBeObscured())
172 return; 144 return;
173 paintFillLayers(paintInfo, backgroundColor, m_layoutBox.style()->backgroundL ayers(), paintRect, bleedAvoidance); 145 paintFillLayers(paintInfo, backgroundColor, m_layoutBox.style()->backgroundL ayers(), paintRect, bleedAvoidance);
174 } 146 }
175 147
176 void BoxPainter::paintRootBoxFillLayers(const PaintInfo& paintInfo) 148 static bool isFillLayerOpaque(const FillLayer& layer, const LayoutObject& imageC lient)
177 { 149 {
178 if (paintInfo.skipRootBackground()) 150 return layer.hasOpaqueImage(&imageClient)
179 return; 151 && layer.image()->canRender(imageClient, imageClient.style()->effectiveZ oom())
180 152 && layer.image()->imageSize(&imageClient, imageClient.style()->effective Zoom()).isEmpty()
181 LayoutObject* rootBackgroundLayoutObject = m_layoutBox.layoutObjectForRootBa ckground(); 153 && layer.hasRepeatXY();
182
183 const FillLayer& bgLayer = rootBackgroundLayoutObject->style()->backgroundLa yers();
184 Color bgColor = rootBackgroundLayoutObject->resolveColor(CSSPropertyBackgrou ndColor);
185
186 paintFillLayers(paintInfo, bgColor, bgLayer, rootBackgroundRect(), Backgroun dBleedNone, SkXfermode::kSrcOver_Mode, rootBackgroundLayoutObject);
187 } 154 }
188 155
189 void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con st FillLayer& fillLayer, const LayoutRect& rect, 156 bool BoxPainter::calculateFillLayerOcclusionCulling(Vector<const FillLayer*, 8> &reversedPaintList, const FillLayer& fillLayer)
190 BackgroundBleedAvoidance bleedAvoidance, SkXfermode::Mode op, LayoutObject* backgroundObject)
191 { 157 {
192 Vector<const FillLayer*, 8> layers; 158 bool isNonAssociative = false;
193 const FillLayer* curLayer = &fillLayer; 159 for (auto currentLayer = &fillLayer; currentLayer; currentLayer = currentLay er->next()) {
194 bool shouldDrawBackgroundInSeparateBuffer = false; 160 reversedPaintList.append(currentLayer);
195 bool isBottomLayerOccluded = false;
196 while (curLayer) {
197 layers.append(curLayer);
198 // Stop traversal when an opaque layer is encountered. 161 // Stop traversal when an opaque layer is encountered.
199 // FIXME : It would be possible for the following occlusion culling test to be more aggressive 162 // FIXME : It would be possible for the following occlusion culling test to be more aggressive
200 // on layers with no repeat by testing whether the image covers the layo ut rect. 163 // on layers with no repeat by testing whether the image covers the layo ut rect.
201 // Testing that here would imply duplicating a lot of calculations that are currently done in 164 // Testing that here would imply duplicating a lot of calculations that are currently done in
202 // LayoutBoxModelObject::paintFillLayerExtended. A more efficient soluti on might be to move 165 // LayoutBoxModelObject::paintFillLayerExtended. A more efficient soluti on might be to move
203 // the layer recursion into paintFillLayerExtended, or to compute the la yer geometry here 166 // the layer recursion into paintFillLayerExtended, or to compute the la yer geometry here
204 // and pass it down. 167 // and pass it down.
205 168
206 if (!shouldDrawBackgroundInSeparateBuffer && curLayer->blendMode() != We bBlendModeNormal) 169 if (currentLayer->composite() != CompositeSourceOver || currentLayer->bl endMode() != WebBlendModeNormal)
207 shouldDrawBackgroundInSeparateBuffer = true; 170 isNonAssociative = true;
208 171
209 if (curLayer->clipOccludesNextLayers() 172 // TODO(trchen): A fill layer cannot paint if the calculated tile size i s empty.
210 && curLayer->hasOpaqueImage(&m_layoutBox) 173 // This occlusion check can be wrong.
211 && curLayer->image()->canRender(m_layoutBox, m_layoutBox.style()->ef fectiveZoom()) 174 if (currentLayer->clipOccludesNextLayers()
chrishtr 2015/06/03 18:15:15 The conditionals here are changed, why is the new
trchen 2015/06/03 23:15:24 (Note that the CL made a mistake, the negation for
212 && curLayer->hasRepeatXY() 175 && isFillLayerOpaque(*currentLayer, m_layoutBox)) {
213 && curLayer->blendMode() == WebBlendModeNormal 176 if (currentLayer->clip() == BorderFillBox)
214 && !m_layoutBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance) ) 177 isNonAssociative = false;
215 break; 178 break;
216 curLayer = curLayer->next(); 179 }
180 }
181 return isNonAssociative;
182 }
183
184 void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con st FillLayer& fillLayer, const LayoutRect& rect, BackgroundBleedAvoidance bleedA voidance, SkXfermode::Mode op, LayoutObject* backgroundObject)
185 {
186 Vector<const FillLayer*, 8> reversedPaintList;
187 bool shouldDrawBackgroundInSeparateBuffer = calculateFillLayerOcclusionCulli ng(reversedPaintList, fillLayer);
188 ASSERT(reversedPaintList.size());
189
190 // If we are responsible to paint box shadow, the bottom layer cannot be ski pped.
191 // TODO(trchen): Box shadow optimization and background color are concepts t hat only
192 // apply to background layers. Ideally we should refactor those out of paint FillLayer.
193 if (m_layoutBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance)) {
194 const FillLayer* bottomLayer = *reversedPaintList.rbegin();
195 if (bottomLayer->next()) {
196 while (bottomLayer->next())
197 bottomLayer = bottomLayer->next();
198 reversedPaintList.append(bottomLayer);
199 }
217 } 200 }
218 201
219 if (layers.size() > 0 && (**layers.rbegin()).next()) 202 const FillLayer& lastLayer = **reversedPaintList.rbegin();
220 isBottomLayerOccluded = true; 203 if (!lastLayer.next()
204 && lastLayer.clip() == BorderFillBox
chrishtr 2015/06/03 18:15:15 What is the BorderFillBox code here and above for?
trchen 2015/06/03 23:15:24 Yes this part can be split as a separate patch. Th
205 && !c.hasAlpha())
206 shouldDrawBackgroundInSeparateBuffer = false;
221 207
222 GraphicsContext* context = paintInfo.context; 208 GraphicsContext* context = paintInfo.context;
223 if (!context) 209 if (!context)
224 shouldDrawBackgroundInSeparateBuffer = false; 210 shouldDrawBackgroundInSeparateBuffer = false;
225 211
226 bool skipBaseColor = false; 212 if (shouldDrawBackgroundInSeparateBuffer)
227 if (shouldDrawBackgroundInSeparateBuffer) { 213 context->beginLayer();
228 bool isBaseColorVisible = !isBottomLayerOccluded && c.hasAlpha();
229 214
230 // Paint the document's base background color outside the transparency l ayer, 215 for (auto it = reversedPaintList.rbegin(); it != reversedPaintList.rend(); + +it)
231 // so that the background images don't blend with this color: http://crb ug.com/389039. 216 paintFillLayer(paintInfo, c, **it, rect, bleedAvoidance, op, backgroundO bject);
232 if (isBaseColorVisible && isDocumentElementWithOpaqueBackground(m_layout Box)) {
233 paintRootBackgroundColor(m_layoutBox, paintInfo, rect, Color());
234 skipBaseColor = true;
235 }
236 context->beginLayer();
237 }
238
239 Vector<const FillLayer*>::const_reverse_iterator topLayer = layers.rend();
240 for (Vector<const FillLayer*>::const_reverse_iterator it = layers.rbegin(); it != topLayer; ++it)
241 paintFillLayer(paintInfo, c, **it, rect, bleedAvoidance, op, backgroundO bject, skipBaseColor);
242 217
243 if (shouldDrawBackgroundInSeparateBuffer) 218 if (shouldDrawBackgroundInSeparateBuffer)
244 context->endLayer(); 219 context->endLayer();
245 } 220 }
246 221
247 void BoxPainter::paintFillLayer(const PaintInfo& paintInfo, const Color& c, cons t FillLayer& fillLayer, const LayoutRect& rect, 222 void BoxPainter::paintFillLayer(const PaintInfo& paintInfo, const Color& c, cons t FillLayer& fillLayer, const LayoutRect& rect,
248 BackgroundBleedAvoidance bleedAvoidance, SkXfermode::Mode op, LayoutObject* backgroundObject, bool skipBaseColor) 223 BackgroundBleedAvoidance bleedAvoidance, SkXfermode::Mode op, LayoutObject* backgroundObject)
249 { 224 {
250 BoxPainter::paintFillLayerExtended(m_layoutBox, paintInfo, c, fillLayer, rec t, bleedAvoidance, 0, LayoutSize(), op, backgroundObject, skipBaseColor); 225 BoxPainter::paintFillLayerExtended(m_layoutBox, paintInfo, c, fillLayer, rec t, bleedAvoidance, 0, LayoutSize(), op, backgroundObject);
251 } 226 }
252 227
253 void BoxPainter::applyBoxShadowForBackground(GraphicsContext* context, LayoutObj ect& obj) 228 void BoxPainter::applyBoxShadowForBackground(GraphicsContext* context, LayoutObj ect& obj)
254 { 229 {
255 const ShadowList* shadowList = obj.style()->boxShadow(); 230 const ShadowList* shadowList = obj.style()->boxShadow();
256 ASSERT(shadowList); 231 ASSERT(shadowList);
257 for (size_t i = shadowList->shadows().size(); i--; ) { 232 for (size_t i = shadowList->shadows().size(); i--; ) {
258 const ShadowData& boxShadow = shadowList->shadows()[i]; 233 const ShadowData& boxShadow = shadowList->shadows()[i];
259 if (boxShadow.style() != Normal) 234 if (boxShadow.style() != Normal)
260 continue; 235 continue;
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 FloatRoundedRect::Radii insetRadii(backgroundRoundedRect.radii()); 289 FloatRoundedRect::Radii insetRadii(backgroundRoundedRect.radii());
315 insetRadii.shrink(-insets.top(), -insets.bottom(), -insets.left(), -inse ts.right()); 290 insetRadii.shrink(-insets.top(), -insets.bottom(), -insets.left(), -inse ts.right());
316 return FloatRoundedRect(insetRect, insetRadii); 291 return FloatRoundedRect(insetRect, insetRadii);
317 } 292 }
318 if (bleedAvoidance == BackgroundBleedBackgroundOverBorder) 293 if (bleedAvoidance == BackgroundBleedBackgroundOverBorder)
319 return obj.style()->getRoundedInnerBorderFor(borderRect, includeLogicalL eftEdge, includeLogicalRightEdge); 294 return obj.style()->getRoundedInnerBorderFor(borderRect, includeLogicalL eftEdge, includeLogicalRightEdge);
320 295
321 return getBackgroundRoundedRect(obj, borderRect, box, boxSize.width(), boxSi ze.height(), includeLogicalLeftEdge, includeLogicalRightEdge); 296 return getBackgroundRoundedRect(obj, borderRect, box, boxSize.width(), boxSi ze.height(), includeLogicalLeftEdge, includeLogicalRightEdge);
322 } 297 }
323 298
324 void BoxPainter::paintFillLayerExtended(LayoutBoxModelObject& obj, const PaintIn fo& paintInfo, const Color& color, const FillLayer& bgLayer, const LayoutRect& r ect, 299 void BoxPainter::paintFillLayerExtended(LayoutBoxModelObject& obj, const PaintIn fo& paintInfo, const Color& color, const FillLayer& bgLayer, const LayoutRect& r ect, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSi ze& boxSize, SkXfermode::Mode op, LayoutObject* backgroundObject)
325 BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSiz e& boxSize, SkXfermode::Mode op, LayoutObject* backgroundObject, bool skipBaseCo lor)
326 { 300 {
327 GraphicsContext* context = paintInfo.context; 301 GraphicsContext* context = paintInfo.context;
328 if (rect.isEmpty()) 302 if (rect.isEmpty())
329 return; 303 return;
330 304
331 bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; 305 bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
332 bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; 306 bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
333 307
334 bool hasRoundedBorder = obj.style()->hasBorderRadius() && (includeLeftEdge | | includeRightEdge); 308 bool hasRoundedBorder = obj.style()->hasBorderRadius() && (includeLeftEdge | | includeRightEdge);
335 bool clippedWithLocalScrolling = obj.hasOverflowClip() && bgLayer.attachment () == LocalBackgroundAttachment; 309 bool clippedWithLocalScrolling = obj.hasOverflowClip() && bgLayer.attachment () == LocalBackgroundAttachment;
336 bool isBorderFill = bgLayer.clip() == BorderFillBox; 310 bool isBorderFill = bgLayer.clip() == BorderFillBox;
337 bool isDocumentElementLayoutObject = obj.isDocumentElement();
338 bool isBottomLayer = !bgLayer.next(); 311 bool isBottomLayer = !bgLayer.next();
339 312
340 Color bgColor = color; 313 Color bgColor = color;
341 StyleImage* bgImage = bgLayer.image(); 314 StyleImage* bgImage = bgLayer.image();
342 bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(obj, obj.sty le()->effectiveZoom());
343 315
344 bool forceBackgroundToWhite = false; 316 bool forceBackgroundToWhite = false;
345 if (obj.document().printing()) { 317 if (obj.document().printing()) {
346 if (obj.style()->printColorAdjust() == PrintColorAdjustEconomy) 318 if (obj.style()->printColorAdjust() == PrintColorAdjustEconomy)
347 forceBackgroundToWhite = true; 319 forceBackgroundToWhite = true;
348 if (obj.document().settings() && obj.document().settings()->shouldPrintB ackgrounds()) 320 if (obj.document().settings() && obj.document().settings()->shouldPrintB ackgrounds())
349 forceBackgroundToWhite = false; 321 forceBackgroundToWhite = false;
350 } 322 }
351 323
352 // When printing backgrounds is disabled or using economy mode, 324 // When printing backgrounds is disabled or using economy mode,
353 // change existing background colors and images to a solid white background. 325 // change existing background colors and images to a solid white background.
354 // If there's no bg color or image, leave it untouched to avoid affecting tr ansparency. 326 // If there's no bg color or image, leave it untouched to avoid affecting tr ansparency.
355 // We don't try to avoid loading the background images, because this style f lag is only set 327 // We don't try to avoid loading the background images, because this style f lag is only set
356 // when printing, and at that point we've already loaded the background imag es anyway. (To avoid 328 // when printing, and at that point we've already loaded the background imag es anyway. (To avoid
357 // loading the background images we'd have to do this check when applying st yles rather than 329 // loading the background images we'd have to do this check when applying st yles rather than
358 // while layout.) 330 // while layout.)
359 if (forceBackgroundToWhite) { 331 if (forceBackgroundToWhite) {
360 // Note that we can't reuse this variable below because the bgColor migh t be changed 332 // Note that we can't reuse this variable below because the bgColor migh t be changed
361 bool shouldPaintBackgroundColor = isBottomLayer && bgColor.alpha(); 333 bool shouldPaintBackgroundColor = isBottomLayer && bgColor.alpha();
362 if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { 334 if (bgImage || shouldPaintBackgroundColor) {
363 bgColor = Color::white; 335 bgColor = Color::white;
364 shouldPaintBackgroundImage = false; 336 bgImage = nullptr;
365 } 337 }
366 } 338 }
367 339
368 bool colorVisible = bgColor.alpha();
369
370 // Fast path for drawing simple color backgrounds. 340 // Fast path for drawing simple color backgrounds.
371 if (!isDocumentElementLayoutObject && !clippedWithLocalScrolling && !shouldP aintBackgroundImage && isBorderFill && isBottomLayer) { 341 if (!clippedWithLocalScrolling && !bgImage && isBorderFill && isBottomLayer) {
372 if (!colorVisible) 342 if (!bgColor.alpha())
373 return; 343 return;
374 344
375 bool boxShadowShouldBeAppliedToBackground = obj.boxShadowShouldBeApplied ToBackground(bleedAvoidance, box); 345 bool boxShadowShouldBeAppliedToBackground = obj.boxShadowShouldBeApplied ToBackground(bleedAvoidance, box);
376 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAp pliedToBackground); 346 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAp pliedToBackground);
377 if (boxShadowShouldBeAppliedToBackground) 347 if (boxShadowShouldBeAppliedToBackground)
378 BoxPainter::applyBoxShadowForBackground(context, obj); 348 BoxPainter::applyBoxShadowForBackground(context, obj);
379 349
380 if (hasRoundedBorder && !bleedAvoidanceIsClipping(bleedAvoidance)) { 350 if (hasRoundedBorder && !bleedAvoidanceIsClipping(bleedAvoidance)) {
381 FloatRoundedRect border = backgroundRoundedRectAdjustedForBleedAvoid ance(obj, rect, 351 FloatRoundedRect border = backgroundRoundedRectAdjustedForBleedAvoid ance(obj, rect,
382 bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) ; 352 bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) ;
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
472 442
473 break; 443 break;
474 } 444 }
475 case BorderFillBox: 445 case BorderFillBox:
476 break; 446 break;
477 default: 447 default:
478 ASSERT_NOT_REACHED(); 448 ASSERT_NOT_REACHED();
479 break; 449 break;
480 } 450 }
481 451
452 BackgroundImageGeometry geometry;
453 if (bgImage)
454 calculateBackgroundImageGeometry(obj, paintInfo.paintContainer(), bgLaye r, scrolledPaintRect, geometry, backgroundObject);
455 bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(obj, obj.sty le()->effectiveZoom());
456
482 // Paint the color first underneath all images, culled if background image o ccludes it. 457 // Paint the color first underneath all images, culled if background image o ccludes it.
483 // FIXME: In the bgLayer->hasFiniteBounds() case, we could improve the culli ng test 458 // TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the cu lling test
484 // by verifying whether the background image covers the entire layout rect. 459 // by verifying whether the background image covers the entire painting area .
485 if (isBottomLayer) { 460 if (isBottomLayer) {
486 IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect)); 461 IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect));
487 bool boxShadowShouldBeAppliedToBackground = obj.boxShadowShouldBeApplied ToBackground(bleedAvoidance, box); 462 bool boxShadowShouldBeAppliedToBackground = obj.boxShadowShouldBeApplied ToBackground(bleedAvoidance, box);
488 bool isOpaqueRoot = (isDocumentElementLayoutObject && !bgColor.hasAlpha( )) || isDocumentElementWithOpaqueBackground(obj); 463 bool backgroundImageOccludesBackgroundColor = shouldPaintBackgroundImage && isFillLayerOpaque(bgLayer, obj);
489 if (boxShadowShouldBeAppliedToBackground || !shouldPaintBackgroundImage || !bgLayer.hasOpaqueImage(&obj) || !bgLayer.hasRepeatXY() || (isOpaqueRoot && ! toLayoutBox(&obj)->size().height())) { 464 if (boxShadowShouldBeAppliedToBackground || !backgroundImageOccludesBack groundColor) {
490 if (!RuntimeEnabledFeatures::slimmingPaintEnabled() && !boxShadowSho uldBeAppliedToBackground) 465 if (!RuntimeEnabledFeatures::slimmingPaintEnabled() && !boxShadowSho uldBeAppliedToBackground)
491 backgroundRect.intersect(paintInfo.rect); 466 backgroundRect.intersect(paintInfo.rect);
492 467
493 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShould BeAppliedToBackground); 468 GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShould BeAppliedToBackground);
494 if (boxShadowShouldBeAppliedToBackground) 469 if (boxShadowShouldBeAppliedToBackground)
495 BoxPainter::applyBoxShadowForBackground(context, obj); 470 BoxPainter::applyBoxShadowForBackground(context, obj);
496 471
497 if (isOpaqueRoot && !skipBaseColor) { 472 if (bgColor.alpha())
498 paintRootBackgroundColor(obj, paintInfo, rect, bgColor);
499 } else if (bgColor.alpha()) {
500 context->fillRect(backgroundRect, bgColor); 473 context->fillRect(backgroundRect, bgColor);
501 }
502 } 474 }
503 } 475 }
504 476
505 // no progressive loading of the background image 477 // no progressive loading of the background image
506 if (shouldPaintBackgroundImage) { 478 if (shouldPaintBackgroundImage) {
507 BackgroundImageGeometry geometry;
508 calculateBackgroundImageGeometry(obj, paintInfo.paintContainer(), bgLaye r, scrolledPaintRect, geometry, backgroundObject);
509 if (!geometry.destRect().isEmpty()) { 479 if (!geometry.destRect().isEmpty()) {
510 SkXfermode::Mode bgOp = WebCoreCompositeToSkiaComposite(bgLayer.comp osite(), bgLayer.blendMode()); 480 SkXfermode::Mode bgOp = WebCoreCompositeToSkiaComposite(bgLayer.comp osite(), bgLayer.blendMode());
511 // if op != SkXfermode::kSrcOver_Mode, a mask is being painted. 481 // if op != SkXfermode::kSrcOver_Mode, a mask is being painted.
512 SkXfermode::Mode compositeOp = op == SkXfermode::kSrcOver_Mode ? bgO p : op; 482 SkXfermode::Mode compositeOp = op == SkXfermode::kSrcOver_Mode ? bgO p : op;
513 LayoutObject* clientForBackgroundImage = backgroundObject ? backgrou ndObject : &obj; 483 LayoutObject* clientForBackgroundImage = backgroundObject ? backgrou ndObject : &obj;
514 RefPtr<Image> image = bgImage->image(clientForBackgroundImage, geome try.tileSize()); 484 RefPtr<Image> image = bgImage->image(clientForBackgroundImage, geome try.tileSize());
515 InterpolationQuality interpolationQuality = chooseInterpolationQuali ty(*clientForBackgroundImage, context, image.get(), &bgLayer, LayoutSize(geometr y.tileSize())); 485 InterpolationQuality interpolationQuality = chooseInterpolationQuali ty(*clientForBackgroundImage, context, image.get(), &bgLayer, LayoutSize(geometr y.tileSize()));
516 if (bgLayer.maskSourceType() == MaskLuminance) 486 if (bgLayer.maskSourceType() == MaskLuminance)
517 context->setColorFilter(ColorFilterLuminanceToAlpha); 487 context->setColorFilter(ColorFilterLuminanceToAlpha);
518 InterpolationQuality previousInterpolationQuality = context->imageIn terpolationQuality(); 488 InterpolationQuality previousInterpolationQuality = context->imageIn terpolationQuality();
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
600 return; 570 return;
601 571
602 IntRect paintRect = pixelSnappedIntRect(LayoutRect(paintOffset, m_layoutBox. size())); 572 IntRect paintRect = pixelSnappedIntRect(LayoutRect(paintOffset, m_layoutBox. size()));
603 LayoutObjectDrawingRecorder drawingRecorder(*paintInfo.context, m_layoutBox, paintInfo.phase, paintRect); 573 LayoutObjectDrawingRecorder drawingRecorder(*paintInfo.context, m_layoutBox, paintInfo.phase, paintRect);
604 if (drawingRecorder.canUseCachedDrawing()) 574 if (drawingRecorder.canUseCachedDrawing())
605 return; 575 return;
606 576
607 paintInfo.context->fillRect(paintRect, Color::black); 577 paintInfo.context->fillRect(paintRect, Color::black);
608 } 578 }
609 579
610 void BoxPainter::paintRootBackgroundColor(LayoutObject& obj, const PaintInfo& pa intInfo, const LayoutRect& rootBackgroundRect, const Color& bgColor)
611 {
612 if (rootBackgroundRect.isEmpty())
613 return;
614
615 ASSERT(obj.isDocumentElement());
616
617 IntRect backgroundRect(pixelSnappedIntRect(rootBackgroundRect));
618 if (!RuntimeEnabledFeatures::slimmingPaintEnabled())
619 backgroundRect.intersect(paintInfo.rect);
620
621 Color baseColor = obj.view()->frameView()->baseBackgroundColor();
622 bool shouldClearDocumentBackground = obj.document().settings() && obj.docume nt().settings()->shouldClearDocumentBackground();
623 SkXfermode::Mode operation = shouldClearDocumentBackground ?
624 SkXfermode::kSrc_Mode : SkXfermode::kSrcOver_Mode;
625
626 GraphicsContext* context = paintInfo.context;
627
628 // If we have an alpha go ahead and blend with the base background color.
629 if (baseColor.alpha()) {
630 if (bgColor.alpha())
631 baseColor = baseColor.blend(bgColor);
632 context->fillRect(backgroundRect, baseColor, operation);
633 } else if (bgColor.alpha()) {
634 context->fillRect(backgroundRect, bgColor, operation);
635 } else if (shouldClearDocumentBackground) {
636 context->clearRect(backgroundRect);
637 }
638 }
639
640 bool BoxPainter::isDocumentElementWithOpaqueBackground(LayoutObject& obj)
641 {
642 if (!obj.isDocumentElement())
643 return false;
644
645 // The background is opaque only if we're the root document, since iframes w ith
646 // no background in the child document should show the parent's background.
647 bool isOpaque = true;
648 Element* ownerElement = obj.document().ownerElement();
649 if (ownerElement) {
650 if (!isHTMLFrameElement(*ownerElement)) {
651 // Locate the <body> element using the DOM. This is easier than tryi ng
652 // to crawl around a layout tree with potential :before/:after conte nt and
653 // anonymous blocks created by inline <body> tags etc. We can locate the <body>
654 // layout object very easily via the DOM.
655 HTMLElement* body = obj.document().body();
656 if (body) {
657 // Can't scroll a frameset document anyway.
658 isOpaque = body->hasTagName(HTMLNames::framesetTag);
659 } else {
660 // FIXME: SVG specific behavior should be in the SVG code.
661 // SVG documents and XML documents with SVG root nodes are trans parent.
662 isOpaque = !obj.document().hasSVGRootNode();
663 }
664 }
665 } else if (obj.view()->frameView()) {
666 isOpaque = !obj.view()->frameView()->isTransparent();
667 }
668
669 return isOpaque;
670 }
671
672 // Return the amount of space to leave between image tiles for the background-re peat: space property. 580 // Return the amount of space to leave between image tiles for the background-re peat: space property.
673 static inline int getSpaceBetweenImageTiles(int areaSize, int tileSize) 581 static inline int getSpaceBetweenImageTiles(int areaSize, int tileSize)
674 { 582 {
675 int numberOfTiles = areaSize / tileSize; 583 int numberOfTiles = areaSize / tileSize;
676 int space = -1; 584 int space = -1;
677 585
678 if (numberOfTiles > 1) { 586 if (numberOfTiles > 1) {
679 // Spec doesn't specify rounding, so use the same method as for backgrou nd-repeat: round. 587 // Spec doesn't specify rounding, so use the same method as for backgrou nd-repeat: round.
680 space = lroundf((areaSize - numberOfTiles * tileSize) / (float)(numberOf Tiles - 1)); 588 space = lroundf((areaSize - numberOfTiles * tileSize) / (float)(numberOf Tiles - 1));
681 } 589 }
682 590
683 return space; 591 return space;
684 } 592 }
685 593
686 void BoxPainter::calculateBackgroundImageGeometry(LayoutBoxModelObject& obj, con st LayoutBoxModelObject* paintContainer, const FillLayer& fillLayer, const Layou tRect& paintRect, 594 void BoxPainter::calculateBackgroundImageGeometry(LayoutBoxModelObject& obj, con st LayoutBoxModelObject* paintContainer, const FillLayer& fillLayer, const Layou tRect& paintRect,
687 BackgroundImageGeometry& geometry, LayoutObject* backgroundObject) 595 BackgroundImageGeometry& geometry, LayoutObject* backgroundObject)
688 { 596 {
689 LayoutUnit left = 0; 597 LayoutUnit left = 0;
690 LayoutUnit top = 0; 598 LayoutUnit top = 0;
691 IntSize positioningAreaSize; 599 IntSize positioningAreaSize;
692 IntRect snappedPaintRect = pixelSnappedIntRect(paintRect); 600 IntRect snappedPaintRect = pixelSnappedIntRect(paintRect);
601 bool isLayoutView = obj.isLayoutView();
602 const LayoutBox* rootBox = nullptr;
603 if (isLayoutView) {
604 rootBox = toLayoutBox(obj.document().documentElement() ? obj.document(). documentElement()->layoutObject() : nullptr);
605 ASSERT(rootBox);
606 }
607 const LayoutBoxModelObject& positioningBox = isLayoutView ? static_cast<cons t LayoutBoxModelObject&>(*rootBox) : obj;
693 608
694 // Determine the background positioning area and set destRect to the backgro und painting area. 609 // Determine the background positioning area and set destRect to the backgro und painting area.
695 // destRect will be adjusted later if the background is non-repeating. 610 // destRect will be adjusted later if the background is non-repeating.
696 // FIXME: transforms spec says that fixed backgrounds behave like scroll ins ide transforms. 611 // FIXME: transforms spec says that fixed backgrounds behave like scroll ins ide transforms.
697 bool fixedAttachment = fillLayer.attachment() == FixedBackgroundAttachment; 612 bool fixedAttachment = fillLayer.attachment() == FixedBackgroundAttachment;
698 613
699 if (RuntimeEnabledFeatures::fastMobileScrollingEnabled()) { 614 if (RuntimeEnabledFeatures::fastMobileScrollingEnabled()) {
700 // As a side effect of an optimization to blit on scroll, we do not hono r the CSS 615 // As a side effect of an optimization to blit on scroll, we do not hono r the CSS
701 // property "background-attachment: fixed" because it may result in rend ering 616 // property "background-attachment: fixed" because it may result in rend ering
702 // artifacts. Note, these artifacts only appear if we are blitting on sc roll of 617 // artifacts. Note, these artifacts only appear if we are blitting on sc roll of
703 // a page that has fixed background images. 618 // a page that has fixed background images.
704 fixedAttachment = false; 619 fixedAttachment = false;
705 } 620 }
706 621
707 if (!fixedAttachment) { 622 if (!fixedAttachment) {
708 geometry.setDestRect(snappedPaintRect); 623 geometry.setDestRect(snappedPaintRect);
709 624
710 LayoutUnit right = 0; 625 LayoutUnit right = 0;
711 LayoutUnit bottom = 0; 626 LayoutUnit bottom = 0;
712 // Scroll and Local. 627 // Scroll and Local.
713 if (fillLayer.origin() != BorderFillBox) { 628 if (fillLayer.origin() != BorderFillBox) {
714 left = obj.borderLeft(); 629 left = positioningBox.borderLeft();
715 right = obj.borderRight(); 630 right = positioningBox.borderRight();
716 top = obj.borderTop(); 631 top = positioningBox.borderTop();
717 bottom = obj.borderBottom(); 632 bottom = positioningBox.borderBottom();
718 if (fillLayer.origin() == ContentFillBox) { 633 if (fillLayer.origin() == ContentFillBox) {
719 left += obj.paddingLeft(); 634 left += positioningBox.paddingLeft();
720 right += obj.paddingRight(); 635 right += positioningBox.paddingRight();
721 top += obj.paddingTop(); 636 top += positioningBox.paddingTop();
722 bottom += obj.paddingBottom(); 637 bottom += positioningBox.paddingBottom();
723 } 638 }
724 } 639 }
725 640
726 // The background of the box generated by the root element covers the en tire canvas including 641 if (isLayoutView) {
727 // its margins. Since those were added in already, we have to factor the m out when computing 642 // The background of the box generated by the root element covers th e entire canvas and will
728 // the background positioning area. 643 // be painted by the view object, but the we should still use the ro ot element box for
729 if (obj.isDocumentElement()) { 644 // positioning.
730 positioningAreaSize = pixelSnappedIntSize(toLayoutBox(&obj)->size() - LayoutSize(left + right, top + bottom), toLayoutBox(&obj)->location()); 645 positioningAreaSize = pixelSnappedIntSize(rootBox->size() - LayoutSi ze(left + right, top + bottom), rootBox->location());
731 // The positioning area is right aligned in paintRect if RightToLeft WritingMode, or left aligned otherwise. 646 // The input paint rect is specified in root element local coordinat e (i.e. a transform
732 if (obj.style()->writingMode() == RightToLeftWritingMode) 647 // is applied on the context for painting), and is expanded to cover the whole canvas.
733 left = paintRect.width() - positioningAreaSize.width() - right - obj.marginRight(); 648 // Since left/top is relative to the paint rect, we need to offset t hem back.
734 else 649 left -= paintRect.x();
735 left += obj.marginLeft(); 650 top -= paintRect.y();
736 top += obj.marginTop();
737 } else { 651 } else {
738 positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutS ize(left + right, top + bottom), paintRect.location()); 652 positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutS ize(left + right, top + bottom), paintRect.location());
739 } 653 }
740 } else { 654 } else {
741 geometry.setHasNonLocalGeometry(); 655 geometry.setHasNonLocalGeometry();
742 656
743 IntRect viewportRect = pixelSnappedIntRect(obj.viewRect()); 657 IntRect viewportRect = pixelSnappedIntRect(obj.viewRect());
744 if (fixedBackgroundPaintsInLocalCoordinates(obj)) 658 if (fixedBackgroundPaintsInLocalCoordinates(obj))
745 viewportRect.setLocation(IntPoint()); 659 viewportRect.setLocation(IntPoint());
746 else if (FrameView* frameView = obj.view()->frameView()) 660 else if (FrameView* frameView = obj.view()->frameView())
747 viewportRect.setLocation(frameView->scrollPosition()); 661 viewportRect.setLocation(frameView->scrollPosition());
748 662
749 if (paintContainer) { 663 if (paintContainer) {
750 IntPoint absoluteContainerOffset = roundedIntPoint(paintContainer->l ocalToAbsolute(FloatPoint())); 664 IntPoint absoluteContainerOffset = roundedIntPoint(paintContainer->l ocalToAbsolute(FloatPoint()));
751 viewportRect.moveBy(-absoluteContainerOffset); 665 viewportRect.moveBy(-absoluteContainerOffset);
752 } 666 }
753 667
754 geometry.setDestRect(viewportRect); 668 geometry.setDestRect(viewportRect);
755 positioningAreaSize = geometry.destRect().size(); 669 positioningAreaSize = geometry.destRect().size();
756 } 670 }
757 671
758 const LayoutObject* clientForBackgroundImage = backgroundObject ? background Object : &obj; 672 const LayoutObject* clientForBackgroundImage = backgroundObject ? background Object : &obj;
759 IntSize fillTileSize = calculateFillTileSize(obj, fillLayer, positioningArea Size); 673 IntSize fillTileSize = calculateFillTileSize(positioningBox, fillLayer, posi tioningAreaSize);
760 fillLayer.image()->setContainerSizeForLayoutObject(clientForBackgroundImage, fillTileSize, obj.style()->effectiveZoom()); 674 fillLayer.image()->setContainerSizeForLayoutObject(clientForBackgroundImage, fillTileSize, obj.style()->effectiveZoom());
761 geometry.setTileSize(fillTileSize); 675 geometry.setTileSize(fillTileSize);
762 676
763 EFillRepeat backgroundRepeatX = fillLayer.repeatX(); 677 EFillRepeat backgroundRepeatX = fillLayer.repeatX();
764 EFillRepeat backgroundRepeatY = fillLayer.repeatY(); 678 EFillRepeat backgroundRepeatY = fillLayer.repeatY();
765 int availableWidth = positioningAreaSize.width() - geometry.tileSize().width (); 679 int availableWidth = positioningAreaSize.width() - geometry.tileSize().width ();
766 int availableHeight = positioningAreaSize.height() - geometry.tileSize().hei ght(); 680 int availableHeight = positioningAreaSize.height() - geometry.tileSize().hei ght();
767 681
768 LayoutUnit computedXPosition = roundedMinimumValueForLength(fillLayer.xPosit ion(), availableWidth); 682 LayoutUnit computedXPosition = roundedMinimumValueForLength(fillLayer.xPosit ion(), availableWidth);
769 if (backgroundRepeatX == RoundFill && positioningAreaSize.width() > 0 && fil lTileSize.width() > 0) { 683 if (backgroundRepeatX == RoundFill && positioningAreaSize.width() > 0 && fil lTileSize.width() > 0) {
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
847 geometry.clip(snappedPaintRect); 761 geometry.clip(snappedPaintRect);
848 } 762 }
849 763
850 InterpolationQuality BoxPainter::chooseInterpolationQuality(LayoutObject& obj, G raphicsContext* context, Image* image, const void* layer, const LayoutSize& size ) 764 InterpolationQuality BoxPainter::chooseInterpolationQuality(LayoutObject& obj, G raphicsContext* context, Image* image, const void* layer, const LayoutSize& size )
851 { 765 {
852 return ImageQualityController::imageQualityController()->chooseInterpolation Quality(context, &obj, image, layer, size); 766 return ImageQualityController::imageQualityController()->chooseInterpolation Quality(context, &obj, image, layer, size);
853 } 767 }
854 768
855 bool BoxPainter::fixedBackgroundPaintsInLocalCoordinates(const LayoutObject& obj ) 769 bool BoxPainter::fixedBackgroundPaintsInLocalCoordinates(const LayoutObject& obj )
856 { 770 {
857 if (!obj.isDocumentElement()) 771 if (!obj.isLayoutView())
858 return false; 772 return false;
859 773
860 if (obj.view()->frameView() && obj.view()->frameView()->paintBehavior() & Pa intBehaviorFlattenCompositingLayers) 774 const LayoutView& view = toLayoutView(obj);
775
776 if (view.frameView() && view.frameView()->paintBehavior() & PaintBehaviorFla ttenCompositingLayers)
861 return false; 777 return false;
862 778
863 DeprecatedPaintLayer* rootLayer = obj.view()->layer(); 779 DeprecatedPaintLayer* rootLayer = view.layer();
864 if (!rootLayer || rootLayer->compositingState() == NotComposited) 780 if (!rootLayer || rootLayer->compositingState() == NotComposited)
865 return false; 781 return false;
866 782
867 return rootLayer->compositedDeprecatedPaintLayerMapping()->backgroundLayerPa intsFixedRootBackground(); 783 return rootLayer->compositedDeprecatedPaintLayerMapping()->backgroundLayerPa intsFixedRootBackground();
868 } 784 }
869 785
870 static inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSize& positioningAreaSize) 786 static inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSize& positioningAreaSize)
871 { 787 {
872 tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tile Size.width().ceil() : tileSize.width().floor()); 788 tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tile Size.width().ceil() : tileSize.width().floor());
873 tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? t ileSize.height().ceil() : tileSize.height().floor()); 789 tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? t ileSize.height().ceil() : tileSize.height().floor());
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after
1123 else 1039 else
1124 clippedEdges |= GraphicsContext::BottomEdge; 1040 clippedEdges |= GraphicsContext::BottomEdge;
1125 } 1041 }
1126 // TODO: support non-integer shadows - crbug.com/334828 1042 // TODO: support non-integer shadows - crbug.com/334828
1127 context->drawInnerShadow(border, shadowColor, flooredIntSize(shadowO ffset), shadowBlur, shadowSpread, clippedEdges); 1043 context->drawInnerShadow(border, shadowColor, flooredIntSize(shadowO ffset), shadowBlur, shadowSpread, clippedEdges);
1128 } 1044 }
1129 } 1045 }
1130 } 1046 }
1131 1047
1132 } // namespace blink 1048 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698