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

Side by Side Diff: third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp

Issue 2506353002: Incrementally build main thread scrolling reasons [spv2] (Closed)
Patch Set: Fix test mistake Created 4 years, 1 month 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
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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 "core/paint/PaintPropertyTreeBuilder.h" 5 #include "core/paint/PaintPropertyTreeBuilder.h"
6 6
7 #include "core/frame/FrameView.h" 7 #include "core/frame/FrameView.h"
8 #include "core/frame/LocalFrame.h" 8 #include "core/frame/LocalFrame.h"
9 #include "core/frame/Settings.h" 9 #include "core/frame/Settings.h"
10 #include "core/layout/LayoutInline.h" 10 #include "core/layout/LayoutInline.h"
11 #include "core/layout/LayoutView.h" 11 #include "core/layout/LayoutView.h"
12 #include "core/layout/svg/LayoutSVGRoot.h" 12 #include "core/layout/svg/LayoutSVGRoot.h"
13 #include "core/paint/FindPropertiesNeedingUpdate.h" 13 #include "core/paint/FindPropertiesNeedingUpdate.h"
14 #include "core/paint/ObjectPaintProperties.h" 14 #include "core/paint/ObjectPaintProperties.h"
15 #include "core/paint/PaintLayer.h" 15 #include "core/paint/PaintLayer.h"
16 #include "core/paint/SVGRootPainter.h" 16 #include "core/paint/SVGRootPainter.h"
17 #include "platform/transforms/TransformationMatrix.h" 17 #include "platform/transforms/TransformationMatrix.h"
18 #include "wtf/PtrUtil.h" 18 #include "wtf/PtrUtil.h"
19 #include <memory> 19 #include <memory>
20 20
21 namespace blink { 21 namespace blink {
22 22
23 PaintPropertyTreeBuilderContext 23 PaintPropertyTreeBuilderContext
24 PaintPropertyTreeBuilder::setupInitialContext() { 24 PaintPropertyTreeBuilder::setupInitialContext() {
25 PaintPropertyTreeBuilderContext context; 25 PaintPropertyTreeBuilderContext context;
26
27 context.current.clip = context.absolutePosition.clip = 26 context.current.clip = context.absolutePosition.clip =
28 context.fixedPosition.clip = ClipPaintPropertyNode::root(); 27 context.fixedPosition.clip = ClipPaintPropertyNode::root();
29 context.currentEffect = EffectPaintPropertyNode::root(); 28 context.currentEffect = EffectPaintPropertyNode::root();
30 context.current.transform = context.absolutePosition.transform = 29 context.current.transform = context.absolutePosition.transform =
31 context.fixedPosition.transform = TransformPaintPropertyNode::root(); 30 context.fixedPosition.transform = TransformPaintPropertyNode::root();
32 context.current.scroll = context.absolutePosition.scroll = 31 context.current.scroll = context.absolutePosition.scroll =
33 context.fixedPosition.scroll = ScrollPaintPropertyNode::root(); 32 context.fixedPosition.scroll = ScrollPaintPropertyNode::root();
34
35 // Ensure scroll tree properties are reset. They will be rebuilt during the
36 // tree walk.
37 ScrollPaintPropertyNode::root()->clearMainThreadScrollingReasons();
38
39 return context; 33 return context;
40 } 34 }
41 35
42 void updateFrameViewPreTranslation( 36 void updateFrameViewPreTranslation(
43 FrameView& frameView, 37 FrameView& frameView,
44 PassRefPtr<const TransformPaintPropertyNode> parent, 38 PassRefPtr<const TransformPaintPropertyNode> parent,
45 const TransformationMatrix& matrix, 39 const TransformationMatrix& matrix,
46 const FloatPoint3D& origin) { 40 const FloatPoint3D& origin) {
47 DCHECK(!RuntimeEnabledFeatures::rootLayerScrollingEnabled()); 41 DCHECK(!RuntimeEnabledFeatures::rootLayerScrollingEnabled());
48 if (auto* existingPreTranslation = frameView.preTranslation()) { 42 if (auto* existingPreTranslation = frameView.preTranslation()) {
(...skipping 28 matching lines...) Expand all
77 if (auto* existingScrollTranslation = frameView.scrollTranslation()) { 71 if (auto* existingScrollTranslation = frameView.scrollTranslation()) {
78 existingScrollTranslation->update(std::move(parent), matrix, origin); 72 existingScrollTranslation->update(std::move(parent), matrix, origin);
79 } else { 73 } else {
80 frameView.setScrollTranslation( 74 frameView.setScrollTranslation(
81 TransformPaintPropertyNode::create(std::move(parent), matrix, origin)); 75 TransformPaintPropertyNode::create(std::move(parent), matrix, origin));
82 } 76 }
83 } 77 }
84 78
85 void updateFrameViewScroll( 79 void updateFrameViewScroll(
86 FrameView& frameView, 80 FrameView& frameView,
87 PassRefPtr<ScrollPaintPropertyNode> parent, 81 PassRefPtr<const ScrollPaintPropertyNode> parent,
88 PassRefPtr<const TransformPaintPropertyNode> scrollOffset, 82 PassRefPtr<const TransformPaintPropertyNode> scrollOffset,
89 const IntSize& clip, 83 const IntSize& clip,
90 const IntSize& bounds, 84 const IntSize& bounds,
91 bool userScrollableHorizontal, 85 bool userScrollableHorizontal,
92 bool userScrollableVertical) { 86 bool userScrollableVertical,
87 MainThreadScrollingReasons mainThreadScrollingReasons) {
93 DCHECK(!RuntimeEnabledFeatures::rootLayerScrollingEnabled()); 88 DCHECK(!RuntimeEnabledFeatures::rootLayerScrollingEnabled());
94 if (auto* existingScroll = frameView.scroll()) { 89 if (auto* existingScroll = frameView.scroll()) {
95 existingScroll->update(std::move(parent), std::move(scrollOffset), clip, 90 existingScroll->update(std::move(parent), std::move(scrollOffset), clip,
96 bounds, userScrollableHorizontal, 91 bounds, userScrollableHorizontal,
97 userScrollableVertical); 92 userScrollableVertical, mainThreadScrollingReasons);
98 } else { 93 } else {
99 frameView.setScroll(ScrollPaintPropertyNode::create( 94 frameView.setScroll(ScrollPaintPropertyNode::create(
100 std::move(parent), std::move(scrollOffset), clip, bounds, 95 std::move(parent), std::move(scrollOffset), clip, bounds,
101 userScrollableHorizontal, userScrollableVertical)); 96 userScrollableHorizontal, userScrollableVertical,
97 mainThreadScrollingReasons));
102 } 98 }
103 } 99 }
104 100
105 void PaintPropertyTreeBuilder::updateProperties( 101 void PaintPropertyTreeBuilder::updateProperties(
106 FrameView& frameView, 102 FrameView& frameView,
107 PaintPropertyTreeBuilderContext& context) { 103 PaintPropertyTreeBuilderContext& context) {
108 if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { 104 if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
109 // With root layer scrolling, the LayoutView (a LayoutObject) properties are 105 // With root layer scrolling, the LayoutView (a LayoutObject) properties are
110 // updated like other objects (see updatePropertiesAndContextForSelf and 106 // updated like other objects (see updatePropertiesAndContextForSelf and
111 // updatePropertiesAndContextForChildren) instead of needing LayoutView- 107 // updatePropertiesAndContextForChildren) instead of needing LayoutView-
(...skipping 29 matching lines...) Expand all
141 frameScroll.translate(-scrollOffset.width(), -scrollOffset.height()); 137 frameScroll.translate(-scrollOffset.width(), -scrollOffset.height());
142 updateFrameViewScrollTranslation(frameView, frameView.preTranslation(), 138 updateFrameViewScrollTranslation(frameView, frameView.preTranslation(),
143 frameScroll, FloatPoint3D()); 139 frameScroll, FloatPoint3D());
144 140
145 IntSize scrollClip = frameView.visibleContentSize(); 141 IntSize scrollClip = frameView.visibleContentSize();
146 IntSize scrollBounds = frameView.contentsSize(); 142 IntSize scrollBounds = frameView.contentsSize();
147 bool userScrollableHorizontal = 143 bool userScrollableHorizontal =
148 frameView.userInputScrollable(HorizontalScrollbar); 144 frameView.userInputScrollable(HorizontalScrollbar);
149 bool userScrollableVertical = 145 bool userScrollableVertical =
150 frameView.userInputScrollable(VerticalScrollbar); 146 frameView.userInputScrollable(VerticalScrollbar);
147
148 MainThreadScrollingReasons reasons = 0;
149 if (!frameView.frame().settings()->threadedScrollingEnabled())
150 reasons |= MainThreadScrollingReason::kThreadedScrollingDisabled;
151 if (frameView.hasBackgroundAttachmentFixedObjects()) {
152 reasons |=
153 MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
154 }
151 updateFrameViewScroll(frameView, context.current.scroll, 155 updateFrameViewScroll(frameView, context.current.scroll,
152 frameView.scrollTranslation(), scrollClip, 156 frameView.scrollTranslation(), scrollClip,
153 scrollBounds, userScrollableHorizontal, 157 scrollBounds, userScrollableHorizontal,
154 userScrollableVertical); 158 userScrollableVertical, reasons);
155 } else { 159 } else {
156 // Ensure pre-existing properties are cleared when there is no scrolling. 160 // Ensure pre-existing properties are cleared when there is no scrolling.
157 frameView.setScrollTranslation(nullptr); 161 frameView.setScrollTranslation(nullptr);
158 frameView.setScroll(nullptr); 162 frameView.setScroll(nullptr);
159 } 163 }
160 } 164 }
161 165
162 // Initialize the context for current, absolute and fixed position cases. 166 // Initialize the context for current, absolute and fixed position cases.
163 // They are the same, except that scroll translation does not apply to 167 // They are the same, except that scroll translation does not apply to
164 // fixed position descendants. 168 // fixed position descendants.
165 const auto* fixedTransformNode = frameView.preTranslation() 169 const auto* fixedTransformNode = frameView.preTranslation()
166 ? frameView.preTranslation() 170 ? frameView.preTranslation()
167 : context.current.transform; 171 : context.current.transform;
168 auto* fixedScrollNode = context.current.scroll; 172 auto* fixedScrollNode = context.current.scroll;
169 DCHECK(frameView.preTranslation()); 173 DCHECK(frameView.preTranslation());
170 context.current.transform = frameView.preTranslation(); 174 context.current.transform = frameView.preTranslation();
171 DCHECK(frameView.contentClip()); 175 DCHECK(frameView.contentClip());
172 context.current.clip = frameView.contentClip(); 176 context.current.clip = frameView.contentClip();
173 if (const auto* scrollTranslation = frameView.scrollTranslation()) 177 if (const auto* scrollTranslation = frameView.scrollTranslation())
174 context.current.transform = scrollTranslation; 178 context.current.transform = scrollTranslation;
175 if (auto* scroll = frameView.scroll()) 179 if (const auto* scroll = frameView.scroll())
176 context.current.scroll = scroll; 180 context.current.scroll = scroll;
177 context.current.paintOffset = LayoutPoint(); 181 context.current.paintOffset = LayoutPoint();
178 context.current.renderingContextID = 0; 182 context.current.renderingContextID = 0;
179 context.current.shouldFlattenInheritedTransform = true; 183 context.current.shouldFlattenInheritedTransform = true;
180 context.absolutePosition = context.current; 184 context.absolutePosition = context.current;
181 context.containerForAbsolutePosition = nullptr; 185 context.containerForAbsolutePosition = nullptr;
182 context.fixedPosition = context.current; 186 context.fixedPosition = context.current;
183 context.fixedPosition.transform = fixedTransformNode; 187 context.fixedPosition.transform = fixedTransformNode;
184 context.fixedPosition.scroll = fixedScrollNode; 188 context.fixedPosition.scroll = fixedScrollNode;
185 189
(...skipping 304 matching lines...) Expand 10 before | Expand all | Expand 10 after
490 needsScrollbarPaintOffset = true; 494 needsScrollbarPaintOffset = true;
491 } 495 }
492 } 496 }
493 } 497 }
494 498
495 auto* properties = object.getMutableForPainting().paintProperties(); 499 auto* properties = object.getMutableForPainting().paintProperties();
496 if (!needsScrollbarPaintOffset && properties) 500 if (!needsScrollbarPaintOffset && properties)
497 properties->clearScrollbarPaintOffset(); 501 properties->clearScrollbarPaintOffset();
498 } 502 }
499 503
500 void PaintPropertyTreeBuilder::updateMainThreadScrollingReasons(
501 const LayoutObject& object,
502 PaintPropertyTreeBuilderContext& context) {
503 // TODO(pdr): Mark properties as needing an update for main thread scroll
504 // reasons and ensure reason changes are propagated to ancestors to account
505 // for the parent walk below. https://crbug.com/664672.
506
507 if (context.current.scroll &&
508 !object.document().settings()->threadedScrollingEnabled()) {
509 context.current.scroll->addMainThreadScrollingReasons(
510 MainThreadScrollingReason::kThreadedScrollingDisabled);
511 }
512
513 if (object.isBackgroundAttachmentFixedObject()) {
514 auto* scrollNode = context.current.scroll;
515 while (
516 scrollNode &&
517 !scrollNode->hasMainThreadScrollingReasons(
518 MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects)) {
519 scrollNode->addMainThreadScrollingReasons(
520 MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
521 scrollNode = scrollNode->parent();
522 }
523 }
524 }
525
526 void PaintPropertyTreeBuilder::updateOverflowClip( 504 void PaintPropertyTreeBuilder::updateOverflowClip(
527 const LayoutObject& object, 505 const LayoutObject& object,
528 PaintPropertyTreeBuilderContext& context) { 506 PaintPropertyTreeBuilderContext& context) {
529 if (!object.isBox()) 507 if (!object.isBox())
530 return; 508 return;
531 509
532 if (object.needsPaintPropertyUpdate()) { 510 if (object.needsPaintPropertyUpdate()) {
533 const LayoutBox& box = toLayoutBox(object); 511 const LayoutBox& box = toLayoutBox(object);
534 // The <input> elements can't have contents thus CSS overflow property 512 // The <input> elements can't have contents thus CSS overflow property
535 // doesn't apply. However for layout purposes we do generate child layout 513 // doesn't apply. However for layout purposes we do generate child layout
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after
663 context.current.transform, matrix, FloatPoint3D(), 641 context.current.transform, matrix, FloatPoint3D(),
664 context.current.shouldFlattenInheritedTransform, 642 context.current.shouldFlattenInheritedTransform,
665 context.current.renderingContextID); 643 context.current.renderingContextID);
666 644
667 IntSize scrollClip = scrollableArea->visibleContentRect().size(); 645 IntSize scrollClip = scrollableArea->visibleContentRect().size();
668 IntSize scrollBounds = scrollableArea->contentsSize(); 646 IntSize scrollBounds = scrollableArea->contentsSize();
669 bool userScrollableHorizontal = 647 bool userScrollableHorizontal =
670 scrollableArea->userInputScrollable(HorizontalScrollbar); 648 scrollableArea->userInputScrollable(HorizontalScrollbar);
671 bool userScrollableVertical = 649 bool userScrollableVertical =
672 scrollableArea->userInputScrollable(VerticalScrollbar); 650 scrollableArea->userInputScrollable(VerticalScrollbar);
651 MainThreadScrollingReasons reasons = 0;
652 if (!object.document().settings()->threadedScrollingEnabled())
653 reasons |= MainThreadScrollingReason::kThreadedScrollingDisabled;
654 // Checking for descendants in the layout tree has two downsides:
655 // 1) There can be more descendants in layout order than in paint
656 // order (e.g., fixed position objects).
657 // 2) Iterating overall all background attachment fixed objects for
658 // every scroll node can be slow, though there will be no objects
659 // in the common case.
660 const FrameView& frameView = *object.frameView();
661 if (frameView.hasBackgroundAttachmentFixedDescendants(object)) {
662 reasons |=
663 MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
664 }
673 object.getMutableForPainting().ensurePaintProperties().updateScroll( 665 object.getMutableForPainting().ensurePaintProperties().updateScroll(
674 context.current.scroll, 666 context.current.scroll,
675 object.paintProperties()->scrollTranslation(), scrollClip, 667 object.paintProperties()->scrollTranslation(), scrollClip,
676 scrollBounds, userScrollableHorizontal, userScrollableVertical); 668 scrollBounds, userScrollableHorizontal, userScrollableVertical,
669 reasons);
677 } else { 670 } else {
678 // Ensure pre-existing properties are cleared when there is no 671 // Ensure pre-existing properties are cleared when there is no
679 // scrolling. 672 // scrolling.
680 auto* properties = object.getMutableForPainting().paintProperties(); 673 auto* properties = object.getMutableForPainting().paintProperties();
681 if (properties) { 674 if (properties) {
682 properties->clearScrollTranslation(); 675 properties->clearScrollTranslation();
683 properties->clearScroll(); 676 properties->clearScroll();
684 } 677 }
685 } 678 }
686 } 679 }
687 } 680 }
688 681
689 if (object.paintProperties() && object.paintProperties()->scroll()) { 682 if (object.paintProperties() && object.paintProperties()->scroll()) {
690 context.current.transform = object.paintProperties()->scrollTranslation(); 683 context.current.transform = object.paintProperties()->scrollTranslation();
691 const auto* scroll = object.paintProperties()->scroll(); 684 context.current.scroll = object.paintProperties()->scroll();
692 // TODO(pdr): Remove this const cast.
693 context.current.scroll = const_cast<ScrollPaintPropertyNode*>(scroll);
694 context.current.shouldFlattenInheritedTransform = false; 685 context.current.shouldFlattenInheritedTransform = false;
695 } 686 }
696 } 687 }
697 688
698 void PaintPropertyTreeBuilder::updateOutOfFlowContext( 689 void PaintPropertyTreeBuilder::updateOutOfFlowContext(
699 const LayoutObject& object, 690 const LayoutObject& object,
700 PaintPropertyTreeBuilderContext& context) { 691 PaintPropertyTreeBuilderContext& context) {
701 if (object.canContainAbsolutePositionObjects()) { 692 if (object.canContainAbsolutePositionObjects()) {
702 context.absolutePosition = context.current; 693 context.absolutePosition = context.current;
703 context.containerForAbsolutePosition = &object; 694 context.containerForAbsolutePosition = &object;
704 } 695 }
705 696
706 if (object.isLayoutView()) { 697 if (object.isLayoutView()) {
707 if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) { 698 if (RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
708 const auto* initialFixedTransform = context.fixedPosition.transform; 699 const auto* initialFixedTransform = context.fixedPosition.transform;
709 auto* initialFixedScroll = context.fixedPosition.scroll; 700 const auto* initialFixedScroll = context.fixedPosition.scroll;
710 701
711 context.fixedPosition = context.current; 702 context.fixedPosition = context.current;
712 703
713 // Fixed position transform and scroll nodes should not be affected. 704 // Fixed position transform and scroll nodes should not be affected.
714 context.fixedPosition.transform = initialFixedTransform; 705 context.fixedPosition.transform = initialFixedTransform;
715 context.fixedPosition.scroll = initialFixedScroll; 706 context.fixedPosition.scroll = initialFixedScroll;
716 } 707 }
717 } else if (object.canContainFixedPositionObjects()) { 708 } else if (object.canContainFixedPositionObjects()) {
718 context.fixedPosition = context.current; 709 context.fixedPosition = context.current;
719 } else if (object.getMutableForPainting().paintProperties() && 710 } else if (object.getMutableForPainting().paintProperties() &&
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
775 containingBlock.paintProperties()->localBorderBoxProperties(); 766 containingBlock.paintProperties()->localBorderBoxProperties();
776 DCHECK(properties); 767 DCHECK(properties);
777 768
778 context.transform = properties->propertyTreeState.transform(); 769 context.transform = properties->propertyTreeState.transform();
779 context.paintOffset = properties->paintOffset; 770 context.paintOffset = properties->paintOffset;
780 context.shouldFlattenInheritedTransform = 771 context.shouldFlattenInheritedTransform =
781 context.transform && context.transform->flattensInheritedTransform(); 772 context.transform && context.transform->flattensInheritedTransform();
782 context.renderingContextID = 773 context.renderingContextID =
783 context.transform ? context.transform->renderingContextID() : 0; 774 context.transform ? context.transform->renderingContextID() : 0;
784 context.clip = properties->propertyTreeState.clip(); 775 context.clip = properties->propertyTreeState.clip();
785 context.scroll = const_cast<ScrollPaintPropertyNode*>( 776 context.scroll = properties->propertyTreeState.scroll();
786 properties->propertyTreeState.scroll());
787 } 777 }
788 778
789 static void deriveBorderBoxFromContainerContext( 779 static void deriveBorderBoxFromContainerContext(
790 const LayoutObject& object, 780 const LayoutObject& object,
791 PaintPropertyTreeBuilderContext& context) { 781 PaintPropertyTreeBuilderContext& context) {
792 if (!object.isBoxModelObject()) 782 if (!object.isBoxModelObject())
793 return; 783 return;
794 784
795 const LayoutBoxModelObject& boxModelObject = toLayoutBoxModelObject(object); 785 const LayoutBoxModelObject& boxModelObject = toLayoutBoxModelObject(object);
796 786
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
881 #endif 871 #endif
882 872
883 deriveBorderBoxFromContainerContext(object, context); 873 deriveBorderBoxFromContainerContext(object, context);
884 874
885 updatePaintOffsetTranslation(object, context); 875 updatePaintOffsetTranslation(object, context);
886 updateTransform(object, context); 876 updateTransform(object, context);
887 updateEffect(object, context); 877 updateEffect(object, context);
888 updateCssClip(object, context); 878 updateCssClip(object, context);
889 updateLocalBorderBoxContext(object, context); 879 updateLocalBorderBoxContext(object, context);
890 updateScrollbarPaintOffset(object, context); 880 updateScrollbarPaintOffset(object, context);
891 updateMainThreadScrollingReasons(object, context);
892 } 881 }
893 882
894 void PaintPropertyTreeBuilder::updatePropertiesForChildren( 883 void PaintPropertyTreeBuilder::updatePropertiesForChildren(
895 const LayoutObject& object, 884 const LayoutObject& object,
896 PaintPropertyTreeBuilderContext& context) { 885 PaintPropertyTreeBuilderContext& context) {
897 if (!object.isBoxModelObject() && !object.isSVG()) 886 if (!object.isBoxModelObject() && !object.isSVG())
898 return; 887 return;
899 888
900 #if DCHECK_IS_ON() 889 #if DCHECK_IS_ON()
901 FindObjectPropertiesNeedingUpdateScope checkNeedsUpdateScope(object); 890 FindObjectPropertiesNeedingUpdateScope checkNeedsUpdateScope(object);
902 #endif 891 #endif
903 892
904 updateOverflowClip(object, context); 893 updateOverflowClip(object, context);
905 updatePerspective(object, context); 894 updatePerspective(object, context);
906 updateSvgLocalToBorderBoxTransform(object, context); 895 updateSvgLocalToBorderBoxTransform(object, context);
907 updateScrollAndScrollTranslation(object, context); 896 updateScrollAndScrollTranslation(object, context);
908 updateOutOfFlowContext(object, context); 897 updateOutOfFlowContext(object, context);
909 } 898 }
910 899
911 } // namespace blink 900 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698