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

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

Issue 2161523002: Isolation of subtrees of stacking contexts and out-of-flow positioned objects (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@CachePaintProperties
Patch Set: - Created 4 years, 5 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
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/LayoutPart.h" 11 #include "core/layout/LayoutPart.h"
12 #include "core/layout/svg/LayoutSVGRoot.h" 12 #include "core/layout/svg/LayoutSVGRoot.h"
13 #include "core/paint/ObjectPaintProperties.h" 13 #include "core/paint/ObjectPaintProperties.h"
14 #include "core/paint/PaintLayer.h" 14 #include "core/paint/PaintLayer.h"
15 #include "core/paint/SVGRootPainter.h" 15 #include "core/paint/SVGRootPainter.h"
16 #include "platform/transforms/TransformationMatrix.h" 16 #include "platform/transforms/TransformationMatrix.h"
17 #include "wtf/PtrUtil.h" 17 #include "wtf/PtrUtil.h"
18 #include <memory> 18 #include <memory>
19 19
20 namespace blink { 20 namespace blink {
21 21
22 void PaintPropertyTreeBuilder::buildTreeRootNodes(FrameView& rootFrame, PaintPro pertyTreeBuilderContext& context) 22 void PaintPropertyTreeBuilder::buildTreeRootNodes(FrameView& rootFrame, PaintPro pertyTreeBuilderContext& context)
23 { 23 {
24 if (!rootFrame.rootTransform() || rootFrame.rootTransform()->parent()) { 24 if (!rootFrame.rootTransform() || rootFrame.rootTransform()->parent()) {
25 rootFrame.setRootTransform(TransformPaintPropertyNode::create(Transforma tionMatrix(), FloatPoint3D(), nullptr)); 25 rootFrame.setRootTransform(TransformPaintPropertyNode::create(Transforma tionMatrix(), FloatPoint3D(), nullptr));
26 rootFrame.setRootClip(ClipPaintPropertyNode::create(rootFrame.rootTransf orm(), FloatRoundedRect(LayoutRect::infiniteIntRect()), nullptr)); 26 rootFrame.setRootClip(ClipPaintPropertyNode::create(rootFrame.rootTransf orm(), FloatRoundedRect(LayoutRect::infiniteIntRect()), nullptr));
27 rootFrame.setRootEffect(EffectPaintPropertyNode::create(1.0, nullptr)); 27 rootFrame.setRootEffect(EffectPaintPropertyNode::create(1.0f, nullptr));
28 } else { 28 } else {
29 DCHECK(rootFrame.rootClip() && !rootFrame.rootClip()->parent()); 29 DCHECK(rootFrame.rootClip() && !rootFrame.rootClip()->parent());
30 DCHECK(rootFrame.rootEffect() && !rootFrame.rootEffect()->parent()); 30 DCHECK(rootFrame.rootEffect() && !rootFrame.rootEffect()->parent());
31 } 31 }
32 32
33 context.current.transform = context.absolutePosition.transform = context.fix edPosition.transform = rootFrame.rootTransform(); 33 context.current.transform = context.absolutePosition.transform = context.fix edPosition.transform = rootFrame.rootTransform();
34 context.current.clip = context.absolutePosition.clip = context.fixedPosition .clip = rootFrame.rootClip(); 34 context.current.clip = context.absolutePosition.clip = context.fixedPosition .clip = rootFrame.rootClip();
35 context.currentEffect = rootFrame.rootEffect(); 35 context.currentEffect = rootFrame.rootEffect();
36 } 36 }
37 37
(...skipping 27 matching lines...) Expand all
65 context.absolutePosition = context.current; 65 context.absolutePosition = context.current;
66 context.containerForAbsolutePosition = nullptr; 66 context.containerForAbsolutePosition = nullptr;
67 context.fixedPosition = context.current; 67 context.fixedPosition = context.current;
68 context.fixedPosition.transform = frameView.preTranslation(); 68 context.fixedPosition.transform = frameView.preTranslation();
69 } 69 }
70 70
71 template < 71 template <
72 typename PropertyNode, 72 typename PropertyNode,
73 PropertyNode* (ObjectPaintProperties::*Getter)() const, 73 PropertyNode* (ObjectPaintProperties::*Getter)() const,
74 void (ObjectPaintProperties::*Setter)(PassRefPtr<PropertyNode>), 74 void (ObjectPaintProperties::*Setter)(PassRefPtr<PropertyNode>),
75 bool (*ComputeProperty)(const LayoutObject&, PaintPropertyTreeBuilderContext &, typename PropertyNode::Value&)> 75 PaintPropertyTreeBuilder::PaintPropertyNodeType (*ComputeProperty)(const Lay outObject&, PaintPropertyTreeBuilderContext&, typename PropertyNode::Value&)>
76 PropertyNode* PaintPropertyTreeBuilder::updateObjectPaintProperty(const LayoutOb ject& object, PaintPropertyTreeBuilderContext& context, PropertyNode*& contextPr operty) 76 PropertyNode* PaintPropertyTreeBuilder::updateObjectPaintProperty(const LayoutOb ject& object, PaintPropertyTreeBuilderContext& context, PropertyNode*& contextPr operty)
77 { 77 {
78 ObjectPaintProperties* previousProperties = object.objectPaintProperties(); 78 ObjectPaintProperties* previousProperties = object.objectPaintProperties();
79 typename PropertyNode::Value newPropertyValue; 79 typename PropertyNode::Value newPropertyValue;
80 if (!ComputeProperty(object, context, newPropertyValue)) { 80 PaintPropertyNodeType nodeType = ComputeProperty(object, context, newPropert yValue);
81 if (nodeType == NoNode) {
81 if (previousProperties) 82 if (previousProperties)
82 (previousProperties->*Setter)(nullptr); 83 (previousProperties->*Setter)(nullptr);
83 return nullptr; 84 return nullptr;
84 } 85 }
85 86
86 PropertyNode* previousPropertyNode = previousProperties ? (previousPropertie s->*Getter)() : nullptr; 87 PropertyNode* previousPropertyNode = previousProperties ? (previousPropertie s->*Getter)() : nullptr;
87 if (previousPropertyNode && previousPropertyNode->value() == newPropertyValu e) { 88 if (previousPropertyNode && previousPropertyNode->value() == newPropertyValu e) {
88 previousPropertyNode->setParent(contextProperty); 89 previousPropertyNode->setParent(contextProperty);
89 contextProperty = previousPropertyNode; 90 contextProperty = previousPropertyNode;
90 return previousPropertyNode; 91 } else {
92 RefPtr<PropertyNode> newPropertyNode = PropertyNode::create(newPropertyV alue, contextProperty);
93 contextProperty = newPropertyNode.get();
94 (object.getMutableForPainting().ensureObjectPaintProperties().*Setter)(n ewPropertyNode.release());
91 } 95 }
92 96
93 RefPtr<PropertyNode> newPropertyNode = PropertyNode::create(newPropertyValue , contextProperty); 97 if (nodeType == IsolationNode)
94 contextProperty = newPropertyNode.get(); 98 contextProperty->setIsIsolationNode();
95 (object.getMutableForPainting().ensureObjectPaintProperties().*Setter)(newPr opertyNode.release());
96 return contextProperty; 99 return contextProperty;
97 } 100 }
98 101
99 bool PaintPropertyTreeBuilder::computePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::V alue& value) 102 // We treat out-of-flow positioned objects and stacking context objects as "pain t property isolation
103 // boundaries". That is, we will create a no-op property nodes if needed that se rve as the isolated
104 // roots of the paint property subtrees, to ensure that there is no paint proper ty parent references
105 // from descendants to ancestors crossing the isolated boundaries. When a whole stacking context is
106 // unchanged in the current document cycle, no property node in the subtree will need update.
107 //
108 // An exception of the above no-reference-crossing-isolation-boundaries rule is for references from a
109 // descendant which is a isolation boundary, e.g. from nodes for an out-of-flow positioned object to
110 // nodes for the container or ancestor of the container of the out-of-flow posit ioned object, crossing
111 // stacking contexts (isolation boundaries) which are are not the containers of the out-of-flow
112 // positioned object. For example, in
113 //
114 // <div id="transform" style="transform: translate(10px, 10px)">
115 // <div id="opacity" style="opacity: 0.5">
116 // <div id="absolute" style="position: absolute"></div>
117 // <div>
118 // </div>
119 //
120 // the property nodes for 'absolute' can directly reference nodes of 'transform' , crossing 'opacity'
121 // which is a stacking context and isolation boundary.
122 //
123 // The exception avoids the necessity to create multiple no-op property nodes fo r normal descendants
124 // and out-of-flow positioned descendants in different tree paths.
125 static bool isPaintPropertyIsolationBoundary(const LayoutObject& object)
126 {
127 return
128 // This condition excludes objects that have stacking-context style but don't actually
129 // create stacking contexts (e.g. SVG objects having opacity).
130 object.hasLayer()
131 // For now we already have property isolation for LayoutView by FrameVie w paint properties.
132 // TODO(wangxianzhu): Revisit this for root-layer-scrolls.
133 && !object.isLayoutView()
134 && (object.isOutOfFlowPositioned() || object.styleRef().isStackingContex t());
135 }
136
137 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput ePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderCont ext& context, TransformPaintPropertyNode::Value& value)
100 { 138 {
101 if (!object.isBoxModelObject()) 139 if (!object.isBoxModelObject())
102 return false; 140 return NoNode;
103 141
104 // TODO(trchen): Eliminate PaintLayer dependency. 142 // TODO(trchen): Eliminate PaintLayer dependency.
105 PaintLayer* layer = toLayoutBoxModelObject(object).layer(); 143 PaintLayer* layer = toLayoutBoxModelObject(object).layer();
106 if (!layer || !layer->paintsWithTransform(GlobalPaintNormalPhase)) 144 if (!layer)
107 return false; 145 return NoNode;
108 146
109 if (context.current.paintOffset == LayoutPoint()) 147 if (context.current.paintOffset == LayoutPoint() || !layer->paintsWithTransf orm(GlobalPaintNormalPhase)) {
110 return false; 148 if (isPaintPropertyIsolationBoundary(object)) {
149 // Will create a no-op paintOffsetTranslation node, if the object is a isolation boundary
150 // and it won't create other transform nodes.
151 if (!object.styleRef().hasTransform() && !object.styleRef().hasPersp ective())
152 return IsolationNode;
153 }
154 return NoNode;
155 }
111 156
112 // We should use the same subpixel paint offset values for snapping regardle ss of whether a 157 // We should use the same subpixel paint offset values for snapping regardle ss of whether a
113 // transform is present. If there is a transform we round the paint offset b ut keep around 158 // transform is present. If there is a transform we round the paint offset b ut keep around
114 // the residual fractional component for the transformed content to paint wi th. 159 // the residual fractional component for the transformed content to paint wi th.
115 // In spv1 this was called "subpixel accumulation". For more information, se e 160 // In spv1 this was called "subpixel accumulation". For more information, se e
116 // PaintLayer::subpixelAccumulation() and PaintLayerPainter::paintFragmentBy ApplyingTransform. 161 // PaintLayer::subpixelAccumulation() and PaintLayerPainter::paintFragmentBy ApplyingTransform.
117 IntPoint roundedPaintOffset = roundedIntPoint(context.current.paintOffset); 162 IntPoint roundedPaintOffset = roundedIntPoint(context.current.paintOffset);
118 LayoutPoint fractionalPaintOffset = LayoutPoint(context.current.paintOffset - roundedPaintOffset); 163 LayoutPoint fractionalPaintOffset = LayoutPoint(context.current.paintOffset - roundedPaintOffset);
119 164
120 value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y()); 165 value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y());
121 context.current.paintOffset = fractionalPaintOffset; 166 context.current.paintOffset = fractionalPaintOffset;
122 return true; 167 return NormalNode;
123 } 168 }
124 169
125 void PaintPropertyTreeBuilder::updatePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) 170 void PaintPropertyTreeBuilder::updatePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context)
126 { 171 {
127 updateObjectPaintProperty< 172 updateObjectPaintProperty<
128 TransformPaintPropertyNode, 173 TransformPaintPropertyNode,
129 &ObjectPaintProperties::paintOffsetTranslation, 174 &ObjectPaintProperties::paintOffsetTranslation,
130 &ObjectPaintProperties::setPaintOffsetTranslation, 175 &ObjectPaintProperties::setPaintOffsetTranslation,
131 &computePaintOffsetTranslation>(object, context, context.current.transfo rm); 176 &computePaintOffsetTranslation>(object, context, context.current.transfo rm);
132 } 177 }
133 178
134 static FloatPoint3D transformOrigin(const LayoutBox& box) 179 static FloatPoint3D transformOrigin(const LayoutBox& box)
135 { 180 {
136 const ComputedStyle& style = box.styleRef(); 181 const ComputedStyle& style = box.styleRef();
137 FloatSize borderBoxSize(box.size()); 182 FloatSize borderBoxSize(box.size());
138 return FloatPoint3D( 183 return FloatPoint3D(
139 floatValueForLength(style.transformOriginX(), borderBoxSize.width()), 184 floatValueForLength(style.transformOriginX(), borderBoxSize.width()),
140 floatValueForLength(style.transformOriginY(), borderBoxSize.height()), 185 floatValueForLength(style.transformOriginY(), borderBoxSize.height()),
141 style.transformOriginZ()); 186 style.transformOriginZ());
142 } 187 }
143 188
144 bool PaintPropertyTreeBuilder::computeTransform(const LayoutObject& object, Pain tPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) 189 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value)
145 { 190 {
146 if (object.isSVG() && !object.isSVGRoot()) { 191 if (object.isSVG() && !object.isSVGRoot()) {
147 // SVG does not use paint offset internally. 192 // SVG does not use paint offset internally.
148 DCHECK(context.current.paintOffset == LayoutPoint()); 193 DCHECK(context.current.paintOffset == LayoutPoint());
149 194
150 // FIXME(pdr): Check for the presence of a transform instead of the valu e. Checking for an 195 // FIXME(pdr): Check for the presence of a transform instead of the valu e. Checking for an
151 // identity matrix will cause the property tree structure to change duri ng animations if 196 // identity matrix will cause the property tree structure to change duri ng animations if
152 // the animation passes through the identity matrix. 197 // the animation passes through the identity matrix.
153 // FIXME(pdr): Refactor this so all non-root SVG objects use the same tr ansform function. 198 // FIXME(pdr): Refactor this so all non-root SVG objects use the same tr ansform function.
154 const AffineTransform& transform = object.isSVGForeignObject() ? object. localSVGTransform() : object.localToSVGParentTransform(); 199 const AffineTransform& transform = object.isSVGForeignObject() ? object. localSVGTransform() : object.localToSVGParentTransform();
155 if (transform.isIdentity()) 200 if (transform.isIdentity())
156 return false; 201 return NoNode;
157 202
158 value.matrix = transform; 203 value.matrix = transform;
159 // The origin is included in the local transform, so leave origin empty. 204 // The origin is included in the local transform, so leave origin empty.
160 return true; 205 return NormalNode;
161 } 206 }
162 207
163 const ComputedStyle& style = object.styleRef(); 208 const ComputedStyle& style = object.styleRef();
164 if (!object.isBox() || !style.hasTransform()) 209 if (!object.isBox() || !style.hasTransform())
165 return false; 210 return NoNode;
166 211
167 style.applyTransform(value.matrix, toLayoutBox(object).size(), ComputedStyle ::ExcludeTransformOrigin, 212 style.applyTransform(value.matrix, toLayoutBox(object).size(), ComputedStyle ::ExcludeTransformOrigin,
168 ComputedStyle::IncludeMotionPath, ComputedStyle::IncludeIndependentTrans formProperties); 213 ComputedStyle::IncludeMotionPath, ComputedStyle::IncludeIndependentTrans formProperties);
169 value.origin = transformOrigin(toLayoutBox(object)); 214 value.origin = transformOrigin(toLayoutBox(object));
170 return true; 215 return NormalNode;
171 } 216 }
172 217
173 void PaintPropertyTreeBuilder::updateTransform(const LayoutObject& object, Paint PropertyTreeBuilderContext& context) 218 void PaintPropertyTreeBuilder::updateTransform(const LayoutObject& object, Paint PropertyTreeBuilderContext& context)
174 { 219 {
175 updateObjectPaintProperty< 220 updateObjectPaintProperty<
176 TransformPaintPropertyNode, 221 TransformPaintPropertyNode,
177 &ObjectPaintProperties::transform, 222 &ObjectPaintProperties::transform,
178 &ObjectPaintProperties::setTransform, 223 &ObjectPaintProperties::setTransform,
179 &computeTransform>(object, context, context.current.transform); 224 &computeTransform>(object, context, context.current.transform);
180 } 225 }
181 226
182 bool PaintPropertyTreeBuilder::computeEffect(const LayoutObject& object, PaintPr opertyTreeBuilderContext& context, EffectPaintPropertyNode::Value& value) 227 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eEffect(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, Ef fectPaintPropertyNode::Value& value)
183 { 228 {
184 if (!object.styleRef().hasOpacity()) 229 if (!object.styleRef().hasOpacity()) {
185 return false; 230 if (isPaintPropertyIsolationBoundary(object)) {
231 // Will create a no-op effect node if the object is a isolation boun dary.
232 value = 1.0f;
233 return IsolationNode;
234 }
235 return NoNode;
236 }
186 237
187 value = object.styleRef().opacity(); 238 value = object.styleRef().opacity();
188 return true; 239 return NormalNode;
189 } 240 }
190 241
191 void PaintPropertyTreeBuilder::updateEffect(const LayoutObject& object, PaintPro pertyTreeBuilderContext& context) 242 void PaintPropertyTreeBuilder::updateEffect(const LayoutObject& object, PaintPro pertyTreeBuilderContext& context)
192 { 243 {
193 updateObjectPaintProperty< 244 updateObjectPaintProperty<
194 EffectPaintPropertyNode, 245 EffectPaintPropertyNode,
195 &ObjectPaintProperties::effect, 246 &ObjectPaintProperties::effect,
196 &ObjectPaintProperties::setEffect, 247 &ObjectPaintProperties::setEffect,
197 &computeEffect>(object, context, context.currentEffect); 248 &computeEffect>(object, context, context.currentEffect);
198 } 249 }
199 250
200 bool PaintPropertyTreeBuilder::computeCssClip(const LayoutObject& object, PaintP ropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) 251 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eCssClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, C lipPaintPropertyNode::Value& value)
201 { 252 {
202 if (!object.hasClip()) 253 if (!object.hasClip())
203 return false; 254 return NoNode;
255
204 DCHECK(object.canContainAbsolutePositionObjects()); 256 DCHECK(object.canContainAbsolutePositionObjects());
205 257
206 // Create clip node for descendants that are not fixed position. 258 // Create clip node for descendants that are not fixed position.
207 // We don't have to setup context.absolutePosition.clip here because this ob ject must be 259 // We don't have to setup context.absolutePosition.clip here because this ob ject must be
208 // a container for absolute position descendants, and will copy from in-flow context later 260 // a container for absolute position descendants, and will copy from in-flow context later
209 // at updateOutOfFlowContext() step. 261 // at updateOutOfFlowContext() step.
210 LayoutRect clipRect = toLayoutBox(object).clipRect(context.current.paintOffs et); 262 LayoutRect clipRect = toLayoutBox(object).clipRect(context.current.paintOffs et);
211 value = FloatRoundedRect(FloatRect(clipRect)); 263 value = FloatRoundedRect(FloatRect(clipRect));
212 return true; 264 return NormalNode;
213 } 265 }
214 266
215 void PaintPropertyTreeBuilder::updateCssClip(const LayoutObject& object, PaintPr opertyTreeBuilderContext& context) 267 void PaintPropertyTreeBuilder::updateCssClip(const LayoutObject& object, PaintPr opertyTreeBuilderContext& context)
216 { 268 {
217 if (ClipPaintPropertyNode* node = updateObjectPaintProperty< 269 if (ClipPaintPropertyNode* node = updateObjectPaintProperty<
218 ClipPaintPropertyNode, 270 ClipPaintPropertyNode,
219 &ObjectPaintProperties::cssClip, 271 &ObjectPaintProperties::cssClip,
220 &ObjectPaintProperties::setCssClip, 272 &ObjectPaintProperties::setCssClip,
221 &computeCssClip>(object, context, context.current.clip)) 273 &computeCssClip>(object, context, context.current.clip))
222 node->setLocalTransformSpace(context.current.transform); 274 node->setLocalTransformSpace(context.current.transform);
223 } 275 }
224 276
225 void PaintPropertyTreeBuilder::updateLocalBorderBoxContext(const LayoutObject& o bject, const PaintPropertyTreeBuilderContext& context) 277 void PaintPropertyTreeBuilder::updateLocalBorderBoxContext(const LayoutObject& o bject, const PaintPropertyTreeBuilderContext& context)
226 { 278 {
227 // Avoid adding an ObjectPaintProperties for non-boxes to save memory, since we don't need them at the moment. 279 // Avoid adding an ObjectPaintProperties for non-boxes to save memory, since we don't need them at the moment.
228 if (!object.isBox() && !object.hasLayer()) 280 if (!object.isBox() && !object.hasLayer())
229 return; 281 return;
230 282
231 std::unique_ptr<ObjectPaintProperties::LocalBorderBoxProperties> borderBoxCo ntext = 283 std::unique_ptr<ObjectPaintProperties::LocalBorderBoxProperties> borderBoxCo ntext =
232 wrapUnique(new ObjectPaintProperties::LocalBorderBoxProperties); 284 wrapUnique(new ObjectPaintProperties::LocalBorderBoxProperties);
233 borderBoxContext->paintOffset = context.current.paintOffset; 285 borderBoxContext->paintOffset = context.current.paintOffset;
234 borderBoxContext->propertyTreeState = PropertyTreeState(context.current.tran sform, context.current.clip, context.currentEffect); 286 borderBoxContext->propertyTreeState = PropertyTreeState(context.current.tran sform, context.current.clip, context.currentEffect);
235 object.getMutableForPainting().ensureObjectPaintProperties().setLocalBorderB oxProperties(std::move(borderBoxContext)); 287 object.getMutableForPainting().ensureObjectPaintProperties().setLocalBorderB oxProperties(std::move(borderBoxContext));
236 } 288 }
237 289
238 // TODO(trchen): Remove this once we bake the paint offset into frameRect. 290 // TODO(trchen): Remove this once we bake the paint offset into frameRect.
239 bool PaintPropertyTreeBuilder::computeScrollbarPaintOffset(const LayoutObject& o bject, const PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNod e::Value& value) 291 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eScrollbarPaintOffset(const LayoutObject& object, const PaintPropertyTreeBuilder Context& context, TransformPaintPropertyNode::Value& value)
240 { 292 {
241 IntPoint roundedPaintOffset = roundedIntPoint(context.current.paintOffset); 293 IntPoint roundedPaintOffset = roundedIntPoint(context.current.paintOffset);
242 if (roundedPaintOffset == IntPoint()) 294 if (roundedPaintOffset == IntPoint())
243 return false; 295 return NoNode;
244 296
245 if (!object.isBoxModelObject()) 297 if (!object.isBoxModelObject())
246 return false; 298 return NoNode;
247 PaintLayerScrollableArea* scrollableArea = toLayoutBoxModelObject(object).ge tScrollableArea(); 299 PaintLayerScrollableArea* scrollableArea = toLayoutBoxModelObject(object).ge tScrollableArea();
248 if (!scrollableArea) 300 if (!scrollableArea)
249 return false; 301 return NoNode;
250 if (!scrollableArea->horizontalScrollbar() && !scrollableArea->verticalScrol lbar()) 302 if (!scrollableArea->horizontalScrollbar() && !scrollableArea->verticalScrol lbar())
251 return false; 303 return NoNode;
252 304
253 value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y()); 305 value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y());
254 return true; 306 return NormalNode;
255 } 307 }
256 308
257 // TODO(trchen): Remove this once we bake the paint offset into frameRect. 309 // TODO(trchen): Remove this once we bake the paint offset into frameRect.
258 void PaintPropertyTreeBuilder::updateScrollbarPaintOffset(const LayoutObject& ob ject, const PaintPropertyTreeBuilderContext& context) 310 void PaintPropertyTreeBuilder::updateScrollbarPaintOffset(const LayoutObject& ob ject, const PaintPropertyTreeBuilderContext& context)
259 { 311 {
260 // This method is almost the same as updateObjectPaintProperty template, exc ept that it doesn't 312 // This method is almost the same as updateObjectPaintProperty template, exc ept that it doesn't
261 // update any context property. 313 // update any context property.
262 ObjectPaintProperties* previousProperties = object.objectPaintProperties(); 314 ObjectPaintProperties* previousProperties = object.objectPaintProperties();
263 TransformPaintPropertyNode::Value paintOffset; 315 TransformPaintPropertyNode::Value paintOffset;
264 if (!computeScrollbarPaintOffset(object, context, paintOffset)) { 316 if (!computeScrollbarPaintOffset(object, context, paintOffset)) {
265 if (previousProperties) 317 if (previousProperties)
266 (previousProperties->setScrollbarPaintOffset(nullptr)); 318 (previousProperties->setScrollbarPaintOffset(nullptr));
267 return; 319 return;
268 } 320 }
269 321
270 TransformPaintPropertyNode* previousScrollbarPaintOffsetNode = previousPrope rties ? previousProperties->scrollbarPaintOffset() : nullptr; 322 TransformPaintPropertyNode* previousScrollbarPaintOffsetNode = previousPrope rties ? previousProperties->scrollbarPaintOffset() : nullptr;
271 if (previousScrollbarPaintOffsetNode && previousScrollbarPaintOffsetNode->va lue() == paintOffset) { 323 if (previousScrollbarPaintOffsetNode && previousScrollbarPaintOffsetNode->va lue() == paintOffset) {
272 previousScrollbarPaintOffsetNode->setParent(context.current.transform); 324 previousScrollbarPaintOffsetNode->setParent(context.current.transform);
273 return; 325 return;
274 } 326 }
275 327
276 object.getMutableForPainting().ensureObjectPaintProperties().setScrollbarPai ntOffset( 328 object.getMutableForPainting().ensureObjectPaintProperties().setScrollbarPai ntOffset(
277 TransformPaintPropertyNode::create(paintOffset, context.current.transfor m)); 329 TransformPaintPropertyNode::create(paintOffset, context.current.transfor m));
278 } 330 }
279 331
280 bool PaintPropertyTreeBuilder::computeOverflowClip(const LayoutObject& object, P aintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) 332 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eOverflowClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& conte xt, ClipPaintPropertyNode::Value& value)
281 { 333 {
282 const LayoutBox& box = toLayoutBox(object); 334 if (object.isBox()) {
335 const LayoutBox& box = toLayoutBox(object);
336 // The <input> elements can't have contents thus CSS overflow property d oesn't apply.
337 // However for layout purposes we do generate child layout objects for t hem, e.g. button label.
338 // We should clip the overflow from those children. This is called contr ol clip and we
339 // technically treat them like overflow clip.
340 if (box.hasControlClip()) {
341 value = FloatRoundedRect(FloatRect(box.controlClipRect(context.curre nt.paintOffset)));
342 return NormalNode;
343 }
344 if (box.hasOverflowClip()) {
345 value = FloatRoundedRect(FloatRect(box.overflowClipRect(context.curr ent.paintOffset)));
346 return NormalNode;
347 }
348 }
283 349
284 // The <input> elements can't have contents thus CSS overflow property doesn 't apply. 350 if (isPaintPropertyIsolationBoundary(object)) {
285 // However for layout purposes we do generate child layout objects for them, e.g. button label. 351 // Will create a no-op overflowClip node, if the object is an isolation boundary
286 // We should clip the overflow from those children. This is called control c lip and we 352 // and it hasn't create a cssClip node.
287 // technically treat them like overflow clip. 353 if (!object.hasClip()) {
288 LayoutRect clipRect; 354 value = FloatRoundedRect(LayoutRect::infiniteIntRect());
289 if (box.hasControlClip()) 355 return IsolationNode;
290 clipRect = box.controlClipRect(context.current.paintOffset); 356 }
291 else if (box.hasOverflowClip()) 357 }
292 clipRect = box.overflowClipRect(context.current.paintOffset); 358 return NoNode;
293 else
294 return false;
295
296 value = FloatRoundedRect(FloatRect(clipRect));
297 return true;
298 } 359 }
299 360
300 void PaintPropertyTreeBuilder::updateOverflowClip(const LayoutObject& object, Pa intPropertyTreeBuilderContext& context) 361 void PaintPropertyTreeBuilder::updateOverflowClip(const LayoutObject& object, Pa intPropertyTreeBuilderContext& context)
301 { 362 {
302 if (!object.isBox())
303 return;
304 const LayoutBox& box = toLayoutBox(object);
305
306 // This need to be in top-level block to hold the reference until we finish creating the normal clip node. 363 // This need to be in top-level block to hold the reference until we finish creating the normal clip node.
307 RefPtr<ClipPaintPropertyNode> borderRadiusClip; 364 RefPtr<ClipPaintPropertyNode> borderRadiusClip;
308 if ((box.hasControlClip() || box.hasOverflowClip()) && box.styleRef().hasBor derRadius()) { 365 if (object.isBox()) {
309 auto innerBorder = box.styleRef().getRoundedInnerBorderFor( 366 const LayoutBox& box = toLayoutBox(object);
310 LayoutRect(context.current.paintOffset, box.size())); 367 if ((box.hasControlClip() || box.hasOverflowClip()) && box.styleRef().ha sBorderRadius()) {
311 borderRadiusClip = ClipPaintPropertyNode::create(context.current.transfo rm, innerBorder, context.current.clip); 368 auto innerBorder = box.styleRef().getRoundedInnerBorderFor(
312 context.current.clip = borderRadiusClip.get(); 369 LayoutRect(context.current.paintOffset, box.size()));
370 borderRadiusClip = ClipPaintPropertyNode::create(context.current.tra nsform, innerBorder, context.current.clip);
371 context.current.clip = borderRadiusClip.get();
372 }
313 } 373 }
314 374
315 if (ClipPaintPropertyNode* node = updateObjectPaintProperty< 375 if (ClipPaintPropertyNode* node = updateObjectPaintProperty<
316 ClipPaintPropertyNode, 376 ClipPaintPropertyNode,
317 &ObjectPaintProperties::overflowClip, 377 &ObjectPaintProperties::overflowClip,
318 &ObjectPaintProperties::setOverflowClip, 378 &ObjectPaintProperties::setOverflowClip,
319 &computeOverflowClip>(object, context, context.current.clip)) 379 &computeOverflowClip>(object, context, context.current.clip))
320 node->setLocalTransformSpace(context.current.transform); 380 node->setLocalTransformSpace(context.current.transform);
321 } 381 }
322 382
323 static FloatPoint perspectiveOrigin(const LayoutBox& box) 383 static FloatPoint perspectiveOrigin(const LayoutBox& box)
324 { 384 {
325 const ComputedStyle& style = box.styleRef(); 385 const ComputedStyle& style = box.styleRef();
326 FloatSize borderBoxSize(box.size()); 386 FloatSize borderBoxSize(box.size());
327 return FloatPoint( 387 return FloatPoint(
328 floatValueForLength(style.perspectiveOriginX(), borderBoxSize.width()), 388 floatValueForLength(style.perspectiveOriginX(), borderBoxSize.width()),
329 floatValueForLength(style.perspectiveOriginY(), borderBoxSize.height())) ; 389 floatValueForLength(style.perspectiveOriginY(), borderBoxSize.height())) ;
330 } 390 }
331 391
332 bool PaintPropertyTreeBuilder::computePerspective(const LayoutObject& object, Pa intPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value ) 392 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput ePerspective(const LayoutObject& object, PaintPropertyTreeBuilderContext& contex t, TransformPaintPropertyNode::Value& value)
333 { 393 {
334 const ComputedStyle& style = object.styleRef(); 394 const ComputedStyle& style = object.styleRef();
335 if (!object.isBox() || !style.hasPerspective()) 395 if (!object.isBox() || !style.hasPerspective())
336 return false; 396 return NoNode;
337 397
338 value.matrix.applyPerspective(style.perspective()); 398 value.matrix.applyPerspective(style.perspective());
339 value.origin = perspectiveOrigin(toLayoutBox(object)) + toLayoutSize(context .current.paintOffset); 399 value.origin = perspectiveOrigin(toLayoutBox(object)) + toLayoutSize(context .current.paintOffset);
340 return true; 400 return NormalNode;
341 } 401 }
342 402
343 void PaintPropertyTreeBuilder::updatePerspective(const LayoutObject& object, Pai ntPropertyTreeBuilderContext& context) 403 void PaintPropertyTreeBuilder::updatePerspective(const LayoutObject& object, Pai ntPropertyTreeBuilderContext& context)
344 { 404 {
345 updateObjectPaintProperty< 405 updateObjectPaintProperty<
346 TransformPaintPropertyNode, 406 TransformPaintPropertyNode,
347 &ObjectPaintProperties::perspective, 407 &ObjectPaintProperties::perspective,
348 &ObjectPaintProperties::setPerspective, 408 &ObjectPaintProperties::setPerspective,
349 &computePerspective>(object, context, context.current.transform); 409 &computePerspective>(object, context, context.current.transform);
350 } 410 }
351 411
352 bool PaintPropertyTreeBuilder::computeSvgLocalToBorderBoxTransform(const LayoutO bject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyN ode::Value& value) 412 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eSvgLocalToBorderBoxTransform(const LayoutObject& object, PaintPropertyTreeBuild erContext& context, TransformPaintPropertyNode::Value& value)
353 { 413 {
354 if (!object.isSVGRoot()) 414 if (!object.isSVGRoot())
355 return false; 415 return NoNode;
356 416
357 AffineTransform transformToBorderBox = SVGRootPainter(toLayoutSVGRoot(object )).transformToPixelSnappedBorderBox(context.current.paintOffset); 417 AffineTransform transformToBorderBox = SVGRootPainter(toLayoutSVGRoot(object )).transformToPixelSnappedBorderBox(context.current.paintOffset);
358 418
359 // The paint offset is included in |transformToBorderBox| so SVG does not ne ed to handle paint 419 // The paint offset is included in |transformToBorderBox| so SVG does not ne ed to handle paint
360 // offset internally. 420 // offset internally.
361 context.current.paintOffset = LayoutPoint(); 421 context.current.paintOffset = LayoutPoint();
362 422
363 if (transformToBorderBox.isIdentity()) 423 if (transformToBorderBox.isIdentity())
364 return false; 424 return NoNode;
365 425
366 value.matrix = transformToBorderBox; 426 value.matrix = transformToBorderBox;
367 context.current.paintOffset = LayoutPoint(); 427 context.current.paintOffset = LayoutPoint();
368 return true; 428 return NormalNode;
369 } 429 }
370 430
371 void PaintPropertyTreeBuilder::updateSvgLocalToBorderBoxTransform(const LayoutOb ject& object, PaintPropertyTreeBuilderContext& context) 431 void PaintPropertyTreeBuilder::updateSvgLocalToBorderBoxTransform(const LayoutOb ject& object, PaintPropertyTreeBuilderContext& context)
372 { 432 {
373 updateObjectPaintProperty< 433 updateObjectPaintProperty<
374 TransformPaintPropertyNode, 434 TransformPaintPropertyNode,
375 &ObjectPaintProperties::svgLocalToBorderBoxTransform, 435 &ObjectPaintProperties::svgLocalToBorderBoxTransform,
376 &ObjectPaintProperties::setSvgLocalToBorderBoxTransform, 436 &ObjectPaintProperties::setSvgLocalToBorderBoxTransform,
377 &computeSvgLocalToBorderBoxTransform>(object, context, context.current.t ransform); 437 &computeSvgLocalToBorderBoxTransform>(object, context, context.current.t ransform);
378 } 438 }
379 439
380 bool PaintPropertyTreeBuilder::computeScrollTranslation(const LayoutObject& obje ct, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) 440 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eScrollTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value)
381 { 441 {
382 if (!object.isBoxModelObject() || !object.hasOverflowClip()) 442 if (!object.isBoxModelObject() || !object.hasOverflowClip())
383 return false; 443 return NoNode;
384 444
385 PaintLayer* layer = toLayoutBoxModelObject(object).layer(); 445 PaintLayer* layer = toLayoutBoxModelObject(object).layer();
386 ASSERT(layer); 446 ASSERT(layer);
387 DoubleSize scrollOffset = layer->getScrollableArea()->scrollOffset(); 447 DoubleSize scrollOffset = layer->getScrollableArea()->scrollOffset();
388 if (scrollOffset.isZero() && !layer->scrollsOverflow()) 448 if (scrollOffset.isZero() && !layer->scrollsOverflow())
389 return false; 449 return NoNode;
390 450
391 value.matrix.translate(-scrollOffset.width(), -scrollOffset.height()); 451 value.matrix.translate(-scrollOffset.width(), -scrollOffset.height());
392 return true; 452 return NormalNode;
393 } 453 }
394 454
395 void PaintPropertyTreeBuilder::updateScrollTranslation(const LayoutObject& objec t, PaintPropertyTreeBuilderContext& context) 455 void PaintPropertyTreeBuilder::updateScrollTranslation(const LayoutObject& objec t, PaintPropertyTreeBuilderContext& context)
396 { 456 {
397 updateObjectPaintProperty< 457 updateObjectPaintProperty<
398 TransformPaintPropertyNode, 458 TransformPaintPropertyNode,
399 &ObjectPaintProperties::scrollTranslation, 459 &ObjectPaintProperties::scrollTranslation,
400 &ObjectPaintProperties::setScrollTranslation, 460 &ObjectPaintProperties::setScrollTranslation,
401 &computeScrollTranslation>(object, context, context.current.transform); 461 &computeScrollTranslation>(object, context, context.current.transform);
402 } 462 }
403 463
404 // Returns true if we need to create a cssClip node for fixed position. 464 // Returns true if we need to create a cssClip node for fixed position.
405 bool PaintPropertyTreeBuilder::computeOutOfFlowContext(const LayoutObject& objec t, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value ) 465 PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::comput eOutOfFlowContext(const LayoutObject& object, PaintPropertyTreeBuilderContext& c ontext, ClipPaintPropertyNode::Value& value)
406 { 466 {
407 if (object.canContainAbsolutePositionObjects()) { 467 if (object.canContainAbsolutePositionObjects()) {
408 context.absolutePosition = context.current; 468 context.absolutePosition = context.current;
409 context.containerForAbsolutePosition = &object; 469 context.containerForAbsolutePosition = &object;
410 } 470 }
411 471
412 // TODO(pdr): Remove the !object.isLayoutView() condition when removing Fram eView 472 // TODO(pdr): Remove the !object.isLayoutView() condition when removing Fram eView
413 // paint properties for rootLayerScrolls. 473 // paint properties for rootLayerScrolls.
414 if (!object.isLayoutView() && object.canContainFixedPositionObjects()) { 474 if (!object.isLayoutView() && object.canContainFixedPositionObjects()) {
415 context.fixedPosition = context.current; 475 context.fixedPosition = context.current;
416 return false; 476 return NoNode;
417 } 477 }
418 478
419 if (!object.objectPaintProperties() || !object.objectPaintProperties()->cssC lip()) 479 if (!object.objectPaintProperties() || !object.objectPaintProperties()->cssC lip())
420 return false; 480 return NoNode;
421 481
422 // CSS clip applies to all descendants, even if this object is not a contain ing block 482 // CSS clip applies to all descendants, even if this object is not a contain ing block
423 // ancestor of the descendant. It is okay for absolute-position descendants because 483 // ancestor of the descendant. It is okay for absolute-position descendants because
424 // having CSS clip implies being absolute position container. However for fi xed-position 484 // having CSS clip implies being absolute position container. However for fi xed-position
425 // descendants we need to insert the clip here if we are not a containing bl ock ancestor 485 // descendants we need to insert the clip here if we are not a containing bl ock ancestor
426 // of them. 486 // of them.
427 auto* cssClip = object.objectPaintProperties()->cssClip(); 487 auto* cssClip = object.objectPaintProperties()->cssClip();
428 488
429 // Before we actually create anything, check whether in-flow context and fix ed-position 489 // Before we actually create anything, check whether in-flow context and fix ed-position
430 // context has exactly the same clip. Reuse if possible. 490 // context has exactly the same clip. Reuse if possible.
431 if (context.fixedPosition.clip == cssClip->parent()) { 491 if (context.fixedPosition.clip == cssClip->parent()) {
432 context.fixedPosition.clip = cssClip; 492 context.fixedPosition.clip = cssClip;
433 return false; 493 return NoNode;
434 } 494 }
435 495
436 value = cssClip->clipRect(); 496 value = cssClip->clipRect();
437 return true; 497 return NormalNode;
438 } 498 }
439 499
440 void PaintPropertyTreeBuilder::updateOutOfFlowContext(const LayoutObject& object , PaintPropertyTreeBuilderContext& context) 500 void PaintPropertyTreeBuilder::updateOutOfFlowContext(const LayoutObject& object , PaintPropertyTreeBuilderContext& context)
441 { 501 {
442 if (ClipPaintPropertyNode* node = updateObjectPaintProperty< 502 if (ClipPaintPropertyNode* node = updateObjectPaintProperty<
443 ClipPaintPropertyNode, 503 ClipPaintPropertyNode,
444 &ObjectPaintProperties::cssClipFixedPosition, 504 &ObjectPaintProperties::cssClipFixedPosition,
445 &ObjectPaintProperties::setCssClipFixedPosition, 505 &ObjectPaintProperties::setCssClipFixedPosition,
446 &computeOutOfFlowContext>(object, context, context.fixedPosition.clip)) 506 &computeOutOfFlowContext>(object, context, context.fixedPosition.clip))
447 node->setLocalTransformSpace(const_cast<TransformPaintPropertyNode*>(obj ect.objectPaintProperties()->cssClip()->localTransformSpace())); 507 node->setLocalTransformSpace(const_cast<TransformPaintPropertyNode*>(obj ect.objectPaintProperties()->cssClip()->localTransformSpace()));
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
489 // but their location have the row's location baked-in. 549 // but their location have the row's location baked-in.
490 // Similar adjustment is done in LayoutTableCell::offsetFromContainer(). 550 // Similar adjustment is done in LayoutTableCell::offsetFromContainer().
491 if (boxModelObject.isTableCell()) { 551 if (boxModelObject.isTableCell()) {
492 LayoutObject* parentRow = boxModelObject.parent(); 552 LayoutObject* parentRow = boxModelObject.parent();
493 ASSERT(parentRow && parentRow->isTableRow()); 553 ASSERT(parentRow && parentRow->isTableRow());
494 context.current.paintOffset.moveBy(-toLayoutBox(parentRow)->topLeftL ocation()); 554 context.current.paintOffset.moveBy(-toLayoutBox(parentRow)->topLeftL ocation());
495 } 555 }
496 } 556 }
497 } 557 }
498 558
559 #if DCHECK_IS_ON()
560 void PaintPropertyTreeBuilder::checkPropertyIsolationBoundaryForStackingContext( const LayoutObject& object)
561 {
562 if (!isPaintPropertyIsolationBoundary(object))
563 return;
564
565 ObjectPaintProperties* properties = object.objectPaintProperties();
566 DCHECK(properties);
567 DCHECK(properties->paintOffsetTranslation() || properties->transform() || pr operties->perspective());
568 DCHECK(properties->effect());
569 DCHECK(properties->cssClip() || properties->overflowClip());
570 }
571 #endif
572
499 void PaintPropertyTreeBuilder::buildTreeNodes(const LayoutObject& object, PaintP ropertyTreeBuilderContext& context) 573 void PaintPropertyTreeBuilder::buildTreeNodes(const LayoutObject& object, PaintP ropertyTreeBuilderContext& context)
500 { 574 {
501 if (!object.isBoxModelObject() && !object.isSVG()) 575 if (!object.isBoxModelObject() && !object.isSVG())
502 return; 576 return;
503 577
504 deriveBorderBoxFromContainerContext(object, context); 578 deriveBorderBoxFromContainerContext(object, context);
505 579
506 updatePaintOffsetTranslation(object, context); 580 updatePaintOffsetTranslation(object, context);
507 updateTransform(object, context); 581 updateTransform(object, context);
508 updateEffect(object, context); 582 updateEffect(object, context);
509 updateCssClip(object, context); 583 updateCssClip(object, context);
510 updateLocalBorderBoxContext(object, context); 584 updateLocalBorderBoxContext(object, context);
511 updateScrollbarPaintOffset(object, context); 585 updateScrollbarPaintOffset(object, context);
512 updateOverflowClip(object, context); 586 updateOverflowClip(object, context);
513 // TODO(trchen): Insert flattening transform here, as specified by 587 // TODO(trchen): Insert flattening transform here, as specified by
514 // http://www.w3.org/TR/css3-transforms/#transform-style-property 588 // http://www.w3.org/TR/css3-transforms/#transform-style-property
515 updatePerspective(object, context); 589 updatePerspective(object, context);
516 updateSvgLocalToBorderBoxTransform(object, context); 590 updateSvgLocalToBorderBoxTransform(object, context);
517 updateScrollTranslation(object, context); 591 updateScrollTranslation(object, context);
518 updateOutOfFlowContext(object, context); 592 updateOutOfFlowContext(object, context);
593 #if DCHECK_IS_ON()
594 checkPropertyIsolationBoundaryForStackingContext(object);
595 #endif
519 } 596 }
520 597
521 } // namespace blink 598 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698