Index: third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp |
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp |
index 76989710eff9a59504b15dbd6a61fbaf23455f2c..0ea39143ab8bc1eb315f76a4c60f5f2803cd496e 100644 |
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp |
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp |
@@ -24,7 +24,7 @@ void PaintPropertyTreeBuilder::buildTreeRootNodes(FrameView& rootFrame, PaintPro |
if (!rootFrame.rootTransform() || rootFrame.rootTransform()->parent()) { |
rootFrame.setRootTransform(TransformPaintPropertyNode::create(TransformationMatrix(), FloatPoint3D(), nullptr)); |
rootFrame.setRootClip(ClipPaintPropertyNode::create(rootFrame.rootTransform(), FloatRoundedRect(LayoutRect::infiniteIntRect()), nullptr)); |
- rootFrame.setRootEffect(EffectPaintPropertyNode::create(1.0, nullptr)); |
+ rootFrame.setRootEffect(EffectPaintPropertyNode::create(1.0f, nullptr)); |
} else { |
DCHECK(rootFrame.rootClip() && !rootFrame.rootClip()->parent()); |
DCHECK(rootFrame.rootEffect() && !rootFrame.rootEffect()->parent()); |
@@ -72,12 +72,13 @@ template < |
typename PropertyNode, |
PropertyNode* (ObjectPaintProperties::*Getter)() const, |
void (ObjectPaintProperties::*Setter)(PassRefPtr<PropertyNode>), |
- bool (*ComputeProperty)(const LayoutObject&, PaintPropertyTreeBuilderContext&, typename PropertyNode::Value&)> |
+ PaintPropertyTreeBuilder::PaintPropertyNodeType (*ComputeProperty)(const LayoutObject&, PaintPropertyTreeBuilderContext&, typename PropertyNode::Value&)> |
PropertyNode* PaintPropertyTreeBuilder::updateObjectPaintProperty(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, PropertyNode*& contextProperty) |
{ |
ObjectPaintProperties* previousProperties = object.objectPaintProperties(); |
typename PropertyNode::Value newPropertyValue; |
- if (!ComputeProperty(object, context, newPropertyValue)) { |
+ PaintPropertyNodeType nodeType = ComputeProperty(object, context, newPropertyValue); |
+ if (nodeType == NoNode) { |
if (previousProperties) |
(previousProperties->*Setter)(nullptr); |
return nullptr; |
@@ -87,27 +88,71 @@ PropertyNode* PaintPropertyTreeBuilder::updateObjectPaintProperty(const LayoutOb |
if (previousPropertyNode && previousPropertyNode->value() == newPropertyValue) { |
previousPropertyNode->setParent(contextProperty); |
contextProperty = previousPropertyNode; |
- return previousPropertyNode; |
+ } else { |
+ RefPtr<PropertyNode> newPropertyNode = PropertyNode::create(newPropertyValue, contextProperty); |
+ contextProperty = newPropertyNode.get(); |
+ (object.getMutableForPainting().ensureObjectPaintProperties().*Setter)(newPropertyNode.release()); |
} |
- RefPtr<PropertyNode> newPropertyNode = PropertyNode::create(newPropertyValue, contextProperty); |
- contextProperty = newPropertyNode.get(); |
- (object.getMutableForPainting().ensureObjectPaintProperties().*Setter)(newPropertyNode.release()); |
+ if (nodeType == IsolationNode) |
+ contextProperty->setIsIsolationNode(); |
return contextProperty; |
} |
-bool PaintPropertyTreeBuilder::computePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+// We treat out-of-flow positioned objects and stacking context objects as "paint property isolation |
+// boundaries". That is, we will create a no-op property nodes if needed that serve as the isolated |
+// roots of the paint property subtrees, to ensure that there is no paint property parent references |
+// from descendants to ancestors crossing the isolated boundaries. When a whole stacking context is |
+// unchanged in the current document cycle, no property node in the subtree will need update. |
+// |
+// An exception of the above no-reference-crossing-isolation-boundaries rule is for references from a |
+// descendant which is a isolation boundary, e.g. from nodes for an out-of-flow positioned object to |
+// nodes for the container or ancestor of the container of the out-of-flow positioned object, crossing |
+// stacking contexts (isolation boundaries) which are are not the containers of the out-of-flow |
+// positioned object. For example, in |
+// |
+// <div id="transform" style="transform: translate(10px, 10px)"> |
+// <div id="opacity" style="opacity: 0.5"> |
+// <div id="absolute" style="position: absolute"></div> |
+// <div> |
+// </div> |
+// |
+// the property nodes for 'absolute' can directly reference nodes of 'transform', crossing 'opacity' |
+// which is a stacking context and isolation boundary. |
+// |
+// The exception avoids the necessity to create multiple no-op property nodes for normal descendants |
+// and out-of-flow positioned descendants in different tree paths. |
+static bool isPaintPropertyIsolationBoundary(const LayoutObject& object) |
+{ |
+ return |
+ // This condition excludes objects that have stacking-context style but don't actually |
+ // create stacking contexts (e.g. SVG objects having opacity). |
+ object.hasLayer() |
+ // For now we already have property isolation for LayoutView by FrameView paint properties. |
+ // TODO(wangxianzhu): Revisit this for root-layer-scrolls. |
+ && !object.isLayoutView() |
+ && (object.isOutOfFlowPositioned() || object.styleRef().isStackingContext()); |
+} |
+ |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
if (!object.isBoxModelObject()) |
- return false; |
+ return NoNode; |
// TODO(trchen): Eliminate PaintLayer dependency. |
PaintLayer* layer = toLayoutBoxModelObject(object).layer(); |
- if (!layer || !layer->paintsWithTransform(GlobalPaintNormalPhase)) |
- return false; |
- |
- if (context.current.paintOffset == LayoutPoint()) |
- return false; |
+ if (!layer) |
+ return NoNode; |
+ |
+ if (context.current.paintOffset == LayoutPoint() || !layer->paintsWithTransform(GlobalPaintNormalPhase)) { |
+ if (isPaintPropertyIsolationBoundary(object)) { |
+ // Will create a no-op paintOffsetTranslation node, if the object is a isolation boundary |
+ // and it won't create other transform nodes. |
+ if (!object.styleRef().hasTransform() && !object.styleRef().hasPerspective()) |
+ return IsolationNode; |
+ } |
+ return NoNode; |
+ } |
// We should use the same subpixel paint offset values for snapping regardless of whether a |
// transform is present. If there is a transform we round the paint offset but keep around |
@@ -119,7 +164,7 @@ bool PaintPropertyTreeBuilder::computePaintOffsetTranslation(const LayoutObject& |
value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y()); |
context.current.paintOffset = fractionalPaintOffset; |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updatePaintOffsetTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -141,7 +186,7 @@ static FloatPoint3D transformOrigin(const LayoutBox& box) |
style.transformOriginZ()); |
} |
-bool PaintPropertyTreeBuilder::computeTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
if (object.isSVG() && !object.isSVGRoot()) { |
// SVG does not use paint offset internally. |
@@ -153,21 +198,21 @@ bool PaintPropertyTreeBuilder::computeTransform(const LayoutObject& object, Pain |
// FIXME(pdr): Refactor this so all non-root SVG objects use the same transform function. |
const AffineTransform& transform = object.isSVGForeignObject() ? object.localSVGTransform() : object.localToSVGParentTransform(); |
if (transform.isIdentity()) |
- return false; |
+ return NoNode; |
value.matrix = transform; |
// The origin is included in the local transform, so leave origin empty. |
- return true; |
+ return NormalNode; |
} |
const ComputedStyle& style = object.styleRef(); |
if (!object.isBox() || !style.hasTransform()) |
- return false; |
+ return NoNode; |
style.applyTransform(value.matrix, toLayoutBox(object).size(), ComputedStyle::ExcludeTransformOrigin, |
ComputedStyle::IncludeMotionPath, ComputedStyle::IncludeIndependentTransformProperties); |
value.origin = transformOrigin(toLayoutBox(object)); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -179,13 +224,19 @@ void PaintPropertyTreeBuilder::updateTransform(const LayoutObject& object, Paint |
&computeTransform>(object, context, context.current.transform); |
} |
-bool PaintPropertyTreeBuilder::computeEffect(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, EffectPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeEffect(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, EffectPaintPropertyNode::Value& value) |
{ |
- if (!object.styleRef().hasOpacity()) |
- return false; |
+ if (!object.styleRef().hasOpacity()) { |
+ if (isPaintPropertyIsolationBoundary(object)) { |
+ // Will create a no-op effect node if the object is a isolation boundary. |
+ value = 1.0f; |
+ return IsolationNode; |
+ } |
+ return NoNode; |
+ } |
value = object.styleRef().opacity(); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateEffect(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -197,10 +248,11 @@ void PaintPropertyTreeBuilder::updateEffect(const LayoutObject& object, PaintPro |
&computeEffect>(object, context, context.currentEffect); |
} |
-bool PaintPropertyTreeBuilder::computeCssClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeCssClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
{ |
if (!object.hasClip()) |
- return false; |
+ return NoNode; |
+ |
DCHECK(object.canContainAbsolutePositionObjects()); |
// Create clip node for descendants that are not fixed position. |
@@ -209,7 +261,7 @@ bool PaintPropertyTreeBuilder::computeCssClip(const LayoutObject& object, PaintP |
// at updateOutOfFlowContext() step. |
LayoutRect clipRect = toLayoutBox(object).clipRect(context.current.paintOffset); |
value = FloatRoundedRect(FloatRect(clipRect)); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateCssClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -236,22 +288,22 @@ void PaintPropertyTreeBuilder::updateLocalBorderBoxContext(const LayoutObject& o |
} |
// TODO(trchen): Remove this once we bake the paint offset into frameRect. |
-bool PaintPropertyTreeBuilder::computeScrollbarPaintOffset(const LayoutObject& object, const PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeScrollbarPaintOffset(const LayoutObject& object, const PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
IntPoint roundedPaintOffset = roundedIntPoint(context.current.paintOffset); |
if (roundedPaintOffset == IntPoint()) |
- return false; |
+ return NoNode; |
if (!object.isBoxModelObject()) |
- return false; |
+ return NoNode; |
PaintLayerScrollableArea* scrollableArea = toLayoutBoxModelObject(object).getScrollableArea(); |
if (!scrollableArea) |
- return false; |
+ return NoNode; |
if (!scrollableArea->horizontalScrollbar() && !scrollableArea->verticalScrollbar()) |
- return false; |
+ return NoNode; |
value.matrix.translate(roundedPaintOffset.x(), roundedPaintOffset.y()); |
- return true; |
+ return NormalNode; |
} |
// TODO(trchen): Remove this once we bake the paint offset into frameRect. |
@@ -277,39 +329,47 @@ void PaintPropertyTreeBuilder::updateScrollbarPaintOffset(const LayoutObject& ob |
TransformPaintPropertyNode::create(paintOffset, context.current.transform)); |
} |
-bool PaintPropertyTreeBuilder::computeOverflowClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeOverflowClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
{ |
- const LayoutBox& box = toLayoutBox(object); |
- |
- // The <input> elements can't have contents thus CSS overflow property doesn't apply. |
- // However for layout purposes we do generate child layout objects for them, e.g. button label. |
- // We should clip the overflow from those children. This is called control clip and we |
- // technically treat them like overflow clip. |
- LayoutRect clipRect; |
- if (box.hasControlClip()) |
- clipRect = box.controlClipRect(context.current.paintOffset); |
- else if (box.hasOverflowClip()) |
- clipRect = box.overflowClipRect(context.current.paintOffset); |
- else |
- return false; |
+ if (object.isBox()) { |
+ const LayoutBox& box = toLayoutBox(object); |
+ // The <input> elements can't have contents thus CSS overflow property doesn't apply. |
+ // However for layout purposes we do generate child layout objects for them, e.g. button label. |
+ // We should clip the overflow from those children. This is called control clip and we |
+ // technically treat them like overflow clip. |
+ if (box.hasControlClip()) { |
+ value = FloatRoundedRect(FloatRect(box.controlClipRect(context.current.paintOffset))); |
+ return NormalNode; |
+ } |
+ if (box.hasOverflowClip()) { |
+ value = FloatRoundedRect(FloatRect(box.overflowClipRect(context.current.paintOffset))); |
+ return NormalNode; |
+ } |
+ } |
- value = FloatRoundedRect(FloatRect(clipRect)); |
- return true; |
+ if (isPaintPropertyIsolationBoundary(object)) { |
+ // Will create a no-op overflowClip node, if the object is an isolation boundary |
+ // and it hasn't create a cssClip node. |
+ if (!object.hasClip()) { |
+ value = FloatRoundedRect(LayoutRect::infiniteIntRect()); |
+ return IsolationNode; |
+ } |
+ } |
+ return NoNode; |
} |
void PaintPropertyTreeBuilder::updateOverflowClip(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
{ |
- if (!object.isBox()) |
- return; |
- const LayoutBox& box = toLayoutBox(object); |
- |
// This need to be in top-level block to hold the reference until we finish creating the normal clip node. |
RefPtr<ClipPaintPropertyNode> borderRadiusClip; |
- if ((box.hasControlClip() || box.hasOverflowClip()) && box.styleRef().hasBorderRadius()) { |
- auto innerBorder = box.styleRef().getRoundedInnerBorderFor( |
- LayoutRect(context.current.paintOffset, box.size())); |
- borderRadiusClip = ClipPaintPropertyNode::create(context.current.transform, innerBorder, context.current.clip); |
- context.current.clip = borderRadiusClip.get(); |
+ if (object.isBox()) { |
+ const LayoutBox& box = toLayoutBox(object); |
+ if ((box.hasControlClip() || box.hasOverflowClip()) && box.styleRef().hasBorderRadius()) { |
+ auto innerBorder = box.styleRef().getRoundedInnerBorderFor( |
+ LayoutRect(context.current.paintOffset, box.size())); |
+ borderRadiusClip = ClipPaintPropertyNode::create(context.current.transform, innerBorder, context.current.clip); |
+ context.current.clip = borderRadiusClip.get(); |
+ } |
} |
if (ClipPaintPropertyNode* node = updateObjectPaintProperty< |
@@ -329,15 +389,15 @@ static FloatPoint perspectiveOrigin(const LayoutBox& box) |
floatValueForLength(style.perspectiveOriginY(), borderBoxSize.height())); |
} |
-bool PaintPropertyTreeBuilder::computePerspective(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computePerspective(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
const ComputedStyle& style = object.styleRef(); |
if (!object.isBox() || !style.hasPerspective()) |
- return false; |
+ return NoNode; |
value.matrix.applyPerspective(style.perspective()); |
value.origin = perspectiveOrigin(toLayoutBox(object)) + toLayoutSize(context.current.paintOffset); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updatePerspective(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -349,10 +409,10 @@ void PaintPropertyTreeBuilder::updatePerspective(const LayoutObject& object, Pai |
&computePerspective>(object, context, context.current.transform); |
} |
-bool PaintPropertyTreeBuilder::computeSvgLocalToBorderBoxTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeSvgLocalToBorderBoxTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
if (!object.isSVGRoot()) |
- return false; |
+ return NoNode; |
AffineTransform transformToBorderBox = SVGRootPainter(toLayoutSVGRoot(object)).transformToPixelSnappedBorderBox(context.current.paintOffset); |
@@ -361,11 +421,11 @@ bool PaintPropertyTreeBuilder::computeSvgLocalToBorderBoxTransform(const LayoutO |
context.current.paintOffset = LayoutPoint(); |
if (transformToBorderBox.isIdentity()) |
- return false; |
+ return NoNode; |
value.matrix = transformToBorderBox; |
context.current.paintOffset = LayoutPoint(); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateSvgLocalToBorderBoxTransform(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -377,19 +437,19 @@ void PaintPropertyTreeBuilder::updateSvgLocalToBorderBoxTransform(const LayoutOb |
&computeSvgLocalToBorderBoxTransform>(object, context, context.current.transform); |
} |
-bool PaintPropertyTreeBuilder::computeScrollTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeScrollTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, TransformPaintPropertyNode::Value& value) |
{ |
if (!object.isBoxModelObject() || !object.hasOverflowClip()) |
- return false; |
+ return NoNode; |
PaintLayer* layer = toLayoutBoxModelObject(object).layer(); |
ASSERT(layer); |
DoubleSize scrollOffset = layer->getScrollableArea()->scrollOffset(); |
if (scrollOffset.isZero() && !layer->scrollsOverflow()) |
- return false; |
+ return NoNode; |
value.matrix.translate(-scrollOffset.width(), -scrollOffset.height()); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateScrollTranslation(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -402,7 +462,7 @@ void PaintPropertyTreeBuilder::updateScrollTranslation(const LayoutObject& objec |
} |
// Returns true if we need to create a cssClip node for fixed position. |
-bool PaintPropertyTreeBuilder::computeOutOfFlowContext(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
+PaintPropertyTreeBuilder::PaintPropertyNodeType PaintPropertyTreeBuilder::computeOutOfFlowContext(const LayoutObject& object, PaintPropertyTreeBuilderContext& context, ClipPaintPropertyNode::Value& value) |
{ |
if (object.canContainAbsolutePositionObjects()) { |
context.absolutePosition = context.current; |
@@ -413,11 +473,11 @@ bool PaintPropertyTreeBuilder::computeOutOfFlowContext(const LayoutObject& objec |
// paint properties for rootLayerScrolls. |
if (!object.isLayoutView() && object.canContainFixedPositionObjects()) { |
context.fixedPosition = context.current; |
- return false; |
+ return NoNode; |
} |
if (!object.objectPaintProperties() || !object.objectPaintProperties()->cssClip()) |
- return false; |
+ return NoNode; |
// CSS clip applies to all descendants, even if this object is not a containing block |
// ancestor of the descendant. It is okay for absolute-position descendants because |
@@ -430,11 +490,11 @@ bool PaintPropertyTreeBuilder::computeOutOfFlowContext(const LayoutObject& objec |
// context has exactly the same clip. Reuse if possible. |
if (context.fixedPosition.clip == cssClip->parent()) { |
context.fixedPosition.clip = cssClip; |
- return false; |
+ return NoNode; |
} |
value = cssClip->clipRect(); |
- return true; |
+ return NormalNode; |
} |
void PaintPropertyTreeBuilder::updateOutOfFlowContext(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
@@ -496,6 +556,20 @@ static void deriveBorderBoxFromContainerContext(const LayoutObject& object, Pain |
} |
} |
+#if DCHECK_IS_ON() |
+void PaintPropertyTreeBuilder::checkPropertyIsolationBoundaryForStackingContext(const LayoutObject& object) |
+{ |
+ if (!isPaintPropertyIsolationBoundary(object)) |
+ return; |
+ |
+ ObjectPaintProperties* properties = object.objectPaintProperties(); |
+ DCHECK(properties); |
+ DCHECK(properties->paintOffsetTranslation() || properties->transform() || properties->perspective()); |
+ DCHECK(properties->effect()); |
+ DCHECK(properties->cssClip() || properties->overflowClip()); |
+} |
+#endif |
+ |
void PaintPropertyTreeBuilder::buildTreeNodes(const LayoutObject& object, PaintPropertyTreeBuilderContext& context) |
{ |
if (!object.isBoxModelObject() && !object.isSVG()) |
@@ -516,6 +590,9 @@ void PaintPropertyTreeBuilder::buildTreeNodes(const LayoutObject& object, PaintP |
updateSvgLocalToBorderBoxTransform(object, context); |
updateScrollTranslation(object, context); |
updateOutOfFlowContext(object, context); |
+#if DCHECK_IS_ON() |
+ checkPropertyIsolationBoundaryForStackingContext(object); |
+#endif |
} |
} // namespace blink |