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
3 // found in the LICENSE file.
8 #include "base/logging.h"
9 #include "cc/base/math_util.h"
10 #include "cc/trees/property_tree.h"
15 PropertyTree
<T
>::PropertyTree()
16 : needs_update_(false) {
17 nodes_
.push_back(T());
19 back()->parent_id
= -1;
23 PropertyTree
<T
>::~PropertyTree() {
26 TransformTree::TransformTree() : source_to_parent_updates_allowed_(true) {}
28 TransformTree::~TransformTree() {
32 int PropertyTree
<T
>::Insert(const T
& tree_node
, int parent_id
) {
33 DCHECK_GT(nodes_
.size(), 0u);
34 nodes_
.push_back(tree_node
);
35 T
& node
= nodes_
.back();
36 node
.parent_id
= parent_id
;
37 node
.id
= static_cast<int>(nodes_
.size()) - 1;
42 void PropertyTree
<T
>::clear() {
44 nodes_
.push_back(T());
46 back()->parent_id
= -1;
49 template class PropertyTree
<TransformNode
>;
50 template class PropertyTree
<ClipNode
>;
51 template class PropertyTree
<EffectNode
>;
53 TransformNodeData::TransformNodeData()
55 content_target_id(-1),
57 needs_local_transform_update(true),
59 ancestors_are_invertible(true),
61 to_screen_is_animated(false),
62 has_only_translation_animations(true),
63 to_screen_has_scale_animation(false),
64 flattens_inherited_transform(false),
65 node_and_ancestors_are_flat(true),
66 node_and_ancestors_have_only_integer_translation(true),
68 needs_sublayer_scale(false),
69 affected_by_inner_viewport_bounds_delta_x(false),
70 affected_by_inner_viewport_bounds_delta_y(false),
71 affected_by_outer_viewport_bounds_delta_x(false),
72 affected_by_outer_viewport_bounds_delta_y(false),
73 layer_scale_factor(1.0f
),
74 post_local_scale_factor(1.0f
),
75 local_maximum_animation_target_scale(0.f
),
76 local_starting_animation_scale(0.f
),
77 combined_maximum_animation_target_scale(0.f
),
78 combined_starting_animation_scale(0.f
) {}
80 TransformNodeData::~TransformNodeData() {
83 void TransformNodeData::update_pre_local_transform(
84 const gfx::Point3F
& transform_origin
) {
85 pre_local
.MakeIdentity();
86 pre_local
.Translate3d(-transform_origin
.x(), -transform_origin
.y(),
87 -transform_origin
.z());
90 void TransformNodeData::update_post_local_transform(
91 const gfx::PointF
& position
,
92 const gfx::Point3F
& transform_origin
) {
93 post_local
.MakeIdentity();
94 post_local
.Scale(post_local_scale_factor
, post_local_scale_factor
);
95 post_local
.Translate3d(
96 position
.x() + source_offset
.x() + transform_origin
.x(),
97 position
.y() + source_offset
.y() + transform_origin
.y(),
98 transform_origin
.z());
101 ClipNodeData::ClipNodeData()
104 inherit_parent_target_space_clip(false),
105 requires_tight_clip_rect(true),
106 render_surface_is_clipped(false) {}
108 EffectNodeData::EffectNodeData() : opacity(1.f
), screen_space_opacity(1.f
) {}
110 void TransformTree::clear() {
111 PropertyTree
<TransformNode
>::clear();
113 nodes_affected_by_inner_viewport_bounds_delta_
.clear();
114 nodes_affected_by_outer_viewport_bounds_delta_
.clear();
117 bool TransformTree::ComputeTransform(int source_id
,
119 gfx::Transform
* transform
) const {
120 transform
->MakeIdentity();
122 if (source_id
== dest_id
)
125 if (source_id
> dest_id
) {
126 return CombineTransformsBetween(source_id
, dest_id
, transform
);
129 return CombineInversesBetween(source_id
, dest_id
, transform
);
132 bool TransformTree::ComputeTransformWithDestinationSublayerScale(
135 gfx::Transform
* transform
) const {
136 bool success
= ComputeTransform(source_id
, dest_id
, transform
);
138 const TransformNode
* dest_node
= Node(dest_id
);
139 if (!dest_node
->data
.needs_sublayer_scale
)
142 transform
->matrix().postScale(dest_node
->data
.sublayer_scale
.x(),
143 dest_node
->data
.sublayer_scale
.y(), 1.f
);
147 bool TransformTree::ComputeTransformWithSourceSublayerScale(
150 gfx::Transform
* transform
) const {
151 bool success
= ComputeTransform(source_id
, dest_id
, transform
);
153 const TransformNode
* source_node
= Node(source_id
);
154 if (!source_node
->data
.needs_sublayer_scale
)
157 if (source_node
->data
.sublayer_scale
.x() == 0 ||
158 source_node
->data
.sublayer_scale
.y() == 0)
161 transform
->Scale(1.f
/ source_node
->data
.sublayer_scale
.x(),
162 1.f
/ source_node
->data
.sublayer_scale
.y());
166 bool TransformTree::Are2DAxisAligned(int source_id
, int dest_id
) const {
167 gfx::Transform transform
;
168 return ComputeTransform(source_id
, dest_id
, &transform
) &&
169 transform
.Preserves2dAxisAlignment();
172 bool TransformTree::NeedsSourceToParentUpdate(TransformNode
* node
) {
173 return (source_to_parent_updates_allowed() &&
174 node
->parent_id
!= node
->data
.source_node_id
);
177 void TransformTree::UpdateTransforms(int id
) {
178 TransformNode
* node
= Node(id
);
179 TransformNode
* parent_node
= parent(node
);
180 TransformNode
* target_node
= Node(node
->data
.target_id
);
181 if (node
->data
.needs_local_transform_update
||
182 NeedsSourceToParentUpdate(node
))
183 UpdateLocalTransform(node
);
186 UpdateScreenSpaceTransform(node
, parent_node
, target_node
);
187 UpdateSublayerScale(node
);
188 UpdateTargetSpaceTransform(node
, target_node
);
189 UpdateAnimationProperties(node
, parent_node
);
190 UpdateSnapping(node
);
191 UpdateNodeAndAncestorsHaveIntegerTranslations(node
, parent_node
);
194 bool TransformTree::IsDescendant(int desc_id
, int source_id
) const {
195 while (desc_id
!= source_id
) {
198 desc_id
= Node(desc_id
)->parent_id
;
203 bool TransformTree::CombineTransformsBetween(int source_id
,
205 gfx::Transform
* transform
) const {
206 DCHECK(source_id
> dest_id
);
207 const TransformNode
* current
= Node(source_id
);
208 const TransformNode
* dest
= Node(dest_id
);
209 // Combine transforms to and from the screen when possible. Since flattening
210 // is a non-linear operation, we cannot use this approach when there is
211 // non-trivial flattening between the source and destination nodes. For
212 // example, consider the tree R->A->B->C, where B flattens its inherited
213 // transform, and A has a non-flat transform. Suppose C is the source and A is
214 // the destination. The expected result is C * B. But C's to_screen
215 // transform is C * B * flattened(A * R), and A's from_screen transform is
216 // R^{-1} * A^{-1}. If at least one of A and R isn't flat, the inverse of
217 // flattened(A * R) won't be R^{-1} * A{-1}, so multiplying C's to_screen and
218 // A's from_screen will not produce the correct result.
219 if (!dest
|| (dest
->data
.ancestors_are_invertible
&&
220 dest
->data
.node_and_ancestors_are_flat
)) {
221 transform
->ConcatTransform(current
->data
.to_screen
);
223 transform
->ConcatTransform(dest
->data
.from_screen
);
227 // Flattening is defined in a way that requires it to be applied while
228 // traversing downward in the tree. We first identify nodes that are on the
229 // path from the source to the destination (this is traversing upward), and
230 // then we visit these nodes in reverse order, flattening as needed. We
231 // early-out if we get to a node whose target node is the destination, since
232 // we can then re-use the target space transform stored at that node. However,
233 // we cannot re-use a stored target space transform if the destination has a
234 // zero sublayer scale, since stored target space transforms have sublayer
235 // scale baked in, but we need to compute an unscaled transform.
236 std::vector
<int> source_to_destination
;
237 source_to_destination
.push_back(current
->id
);
238 current
= parent(current
);
239 bool destination_has_non_zero_sublayer_scale
=
240 dest
->data
.sublayer_scale
.x() != 0.f
&&
241 dest
->data
.sublayer_scale
.y() != 0.f
;
242 DCHECK(destination_has_non_zero_sublayer_scale
||
243 !dest
->data
.ancestors_are_invertible
);
244 for (; current
&& current
->id
> dest_id
; current
= parent(current
)) {
245 if (destination_has_non_zero_sublayer_scale
&&
246 current
->data
.target_id
== dest_id
&&
247 current
->data
.content_target_id
== dest_id
)
249 source_to_destination
.push_back(current
->id
);
252 gfx::Transform combined_transform
;
253 if (current
->id
> dest_id
) {
254 combined_transform
= current
->data
.to_target
;
255 // The stored target space transform has sublayer scale baked in, but we
256 // need the unscaled transform.
257 combined_transform
.Scale(1.0f
/ dest
->data
.sublayer_scale
.x(),
258 1.0f
/ dest
->data
.sublayer_scale
.y());
259 } else if (current
->id
< dest_id
) {
260 // We have reached the lowest common ancestor of the source and destination
261 // nodes. This case can occur when we are transforming between a node
262 // corresponding to a fixed-position layer (or its descendant) and the node
263 // corresponding to the layer's render target. For example, consider the
264 // layer tree R->T->S->F where F is fixed-position, S owns a render surface,
265 // and T has a significant transform. This will yield the following
272 // In this example, T will have id 2, S will have id 3, and F will have id
273 // 4. When walking up the ancestor chain from F, the first node with a
274 // smaller id than S will be T, the lowest common ancestor of these nodes.
275 // We compute the transform from T to S here, and then from F to T in the
277 DCHECK(IsDescendant(dest_id
, current
->id
));
278 CombineInversesBetween(current
->id
, dest_id
, &combined_transform
);
279 DCHECK(combined_transform
.IsApproximatelyIdentityOrTranslation(
280 SkDoubleToMScalar(1e-4)));
283 size_t source_to_destination_size
= source_to_destination
.size();
284 for (size_t i
= 0; i
< source_to_destination_size
; ++i
) {
285 size_t index
= source_to_destination_size
- 1 - i
;
286 const TransformNode
* node
= Node(source_to_destination
[index
]);
287 if (node
->data
.flattens_inherited_transform
)
288 combined_transform
.FlattenTo2d();
289 combined_transform
.PreconcatTransform(node
->data
.to_parent
);
292 transform
->ConcatTransform(combined_transform
);
296 bool TransformTree::CombineInversesBetween(int source_id
,
298 gfx::Transform
* transform
) const {
299 DCHECK(source_id
< dest_id
);
300 const TransformNode
* current
= Node(dest_id
);
301 const TransformNode
* dest
= Node(source_id
);
302 // Just as in CombineTransformsBetween, we can use screen space transforms in
303 // this computation only when there isn't any non-trivial flattening
305 if (current
->data
.ancestors_are_invertible
&&
306 current
->data
.node_and_ancestors_are_flat
) {
307 transform
->PreconcatTransform(current
->data
.from_screen
);
309 transform
->PreconcatTransform(dest
->data
.to_screen
);
313 // Inverting a flattening is not equivalent to flattening an inverse. This
314 // means we cannot, for example, use the inverse of each node's to_parent
315 // transform, flattening where needed. Instead, we must compute the transform
316 // from the destination to the source, with flattening, and then invert the
318 gfx::Transform dest_to_source
;
319 CombineTransformsBetween(dest_id
, source_id
, &dest_to_source
);
320 gfx::Transform source_to_dest
;
321 bool all_are_invertible
= dest_to_source
.GetInverse(&source_to_dest
);
322 transform
->PreconcatTransform(source_to_dest
);
323 return all_are_invertible
;
326 void TransformTree::UpdateLocalTransform(TransformNode
* node
) {
327 gfx::Transform transform
= node
->data
.post_local
;
328 if (NeedsSourceToParentUpdate(node
)) {
329 gfx::Transform to_parent
;
330 ComputeTransform(node
->data
.source_node_id
, node
->parent_id
, &to_parent
);
331 node
->data
.source_to_parent
= to_parent
.To2dTranslation();
334 gfx::Vector2dF fixed_position_adjustment
;
335 if (node
->data
.affected_by_inner_viewport_bounds_delta_x
)
336 fixed_position_adjustment
.set_x(inner_viewport_bounds_delta_
.x());
337 else if (node
->data
.affected_by_outer_viewport_bounds_delta_x
)
338 fixed_position_adjustment
.set_x(outer_viewport_bounds_delta_
.x());
340 if (node
->data
.affected_by_inner_viewport_bounds_delta_y
)
341 fixed_position_adjustment
.set_y(inner_viewport_bounds_delta_
.y());
342 else if (node
->data
.affected_by_outer_viewport_bounds_delta_y
)
343 fixed_position_adjustment
.set_y(outer_viewport_bounds_delta_
.y());
346 node
->data
.source_to_parent
.x() - node
->data
.scroll_offset
.x() +
347 fixed_position_adjustment
.x(),
348 node
->data
.source_to_parent
.y() - node
->data
.scroll_offset
.y() +
349 fixed_position_adjustment
.y());
350 transform
.PreconcatTransform(node
->data
.local
);
351 transform
.PreconcatTransform(node
->data
.pre_local
);
352 node
->data
.set_to_parent(transform
);
353 node
->data
.needs_local_transform_update
= false;
356 void TransformTree::UpdateScreenSpaceTransform(TransformNode
* node
,
357 TransformNode
* parent_node
,
358 TransformNode
* target_node
) {
360 node
->data
.to_screen
= node
->data
.to_parent
;
361 node
->data
.ancestors_are_invertible
= true;
362 node
->data
.to_screen_is_animated
= false;
363 node
->data
.node_and_ancestors_are_flat
= node
->data
.to_parent
.IsFlat();
365 node
->data
.to_screen
= parent_node
->data
.to_screen
;
366 if (node
->data
.flattens_inherited_transform
)
367 node
->data
.to_screen
.FlattenTo2d();
368 node
->data
.to_screen
.PreconcatTransform(node
->data
.to_parent
);
369 node
->data
.ancestors_are_invertible
=
370 parent_node
->data
.ancestors_are_invertible
;
371 node
->data
.node_and_ancestors_are_flat
=
372 parent_node
->data
.node_and_ancestors_are_flat
&&
373 node
->data
.to_parent
.IsFlat();
376 if (!node
->data
.to_screen
.GetInverse(&node
->data
.from_screen
))
377 node
->data
.ancestors_are_invertible
= false;
380 void TransformTree::UpdateSublayerScale(TransformNode
* node
) {
381 // The sublayer scale depends on the screen space transform, so update it too.
382 node
->data
.sublayer_scale
=
383 node
->data
.needs_sublayer_scale
384 ? MathUtil::ComputeTransform2dScaleComponents(
385 node
->data
.to_screen
, node
->data
.layer_scale_factor
)
386 : gfx::Vector2dF(1.0f
, 1.0f
);
389 void TransformTree::UpdateTargetSpaceTransform(TransformNode
* node
,
390 TransformNode
* target_node
) {
391 if (node
->data
.needs_sublayer_scale
) {
392 node
->data
.to_target
.MakeIdentity();
393 node
->data
.to_target
.Scale(node
->data
.sublayer_scale
.x(),
394 node
->data
.sublayer_scale
.y());
396 const bool target_is_root_surface
= target_node
->id
== 1;
397 // In order to include the root transform for the root surface, we walk up
398 // to the root of the transform tree in ComputeTransform.
399 int target_id
= target_is_root_surface
? 0 : target_node
->id
;
400 ComputeTransformWithDestinationSublayerScale(node
->id
, target_id
,
401 &node
->data
.to_target
);
404 if (!node
->data
.to_target
.GetInverse(&node
->data
.from_target
))
405 node
->data
.ancestors_are_invertible
= false;
408 void TransformTree::UpdateAnimationProperties(TransformNode
* node
,
409 TransformNode
* parent_node
) {
410 bool ancestor_is_animating
= false;
411 bool ancestor_is_animating_scale
= false;
412 float ancestor_maximum_target_scale
= 0.f
;
413 float ancestor_starting_animation_scale
= 0.f
;
415 ancestor_is_animating
= parent_node
->data
.to_screen_is_animated
;
416 ancestor_is_animating_scale
=
417 parent_node
->data
.to_screen_has_scale_animation
;
418 ancestor_maximum_target_scale
=
419 parent_node
->data
.combined_maximum_animation_target_scale
;
420 ancestor_starting_animation_scale
=
421 parent_node
->data
.combined_starting_animation_scale
;
423 node
->data
.to_screen_is_animated
=
424 node
->data
.is_animated
|| ancestor_is_animating
;
425 node
->data
.to_screen_has_scale_animation
=
426 !node
->data
.has_only_translation_animations
||
427 ancestor_is_animating_scale
;
429 // Once we've failed to compute a maximum animated scale at an ancestor, we
431 bool failed_at_ancestor
=
432 ancestor_is_animating_scale
&& ancestor_maximum_target_scale
== 0.f
;
434 // Computing maximum animated scale in the presence of non-scale/translation
435 // transforms isn't supported.
436 bool failed_for_non_scale_or_translation
=
437 !node
->data
.to_target
.IsScaleOrTranslation();
439 // We don't attempt to accumulate animation scale from multiple nodes with
440 // scale animations, because of the risk of significant overestimation. For
441 // example, one node might be increasing scale from 1 to 10 at the same time
442 // as another node is decreasing scale from 10 to 1. Naively combining these
443 // scales would produce a scale of 100.
444 bool failed_for_multiple_scale_animations
=
445 ancestor_is_animating_scale
&&
446 !node
->data
.has_only_translation_animations
;
448 if (failed_at_ancestor
|| failed_for_non_scale_or_translation
||
449 failed_for_multiple_scale_animations
) {
450 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
451 node
->data
.combined_starting_animation_scale
= 0.f
;
453 // This ensures that descendants know we've failed to compute a maximum
455 node
->data
.to_screen_has_scale_animation
= true;
459 if (!node
->data
.to_screen_has_scale_animation
) {
460 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
461 node
->data
.combined_starting_animation_scale
= 0.f
;
465 // At this point, we know exactly one of this node or an ancestor is animating
467 if (node
->data
.has_only_translation_animations
) {
468 // An ancestor is animating scale.
469 gfx::Vector2dF local_scales
=
470 MathUtil::ComputeTransform2dScaleComponents(node
->data
.local
, 0.f
);
471 float max_local_scale
= std::max(local_scales
.x(), local_scales
.y());
472 node
->data
.combined_maximum_animation_target_scale
=
473 max_local_scale
* ancestor_maximum_target_scale
;
474 node
->data
.combined_starting_animation_scale
=
475 max_local_scale
* ancestor_starting_animation_scale
;
479 if (node
->data
.local_starting_animation_scale
== 0.f
||
480 node
->data
.local_maximum_animation_target_scale
== 0.f
) {
481 node
->data
.combined_maximum_animation_target_scale
= 0.f
;
482 node
->data
.combined_starting_animation_scale
= 0.f
;
486 gfx::Vector2dF ancestor_scales
=
487 parent_node
? MathUtil::ComputeTransform2dScaleComponents(
488 parent_node
->data
.to_target
, 0.f
)
489 : gfx::Vector2dF(1.f
, 1.f
);
490 float max_ancestor_scale
= std::max(ancestor_scales
.x(), ancestor_scales
.y());
491 node
->data
.combined_maximum_animation_target_scale
=
492 max_ancestor_scale
* node
->data
.local_maximum_animation_target_scale
;
493 node
->data
.combined_starting_animation_scale
=
494 max_ancestor_scale
* node
->data
.local_starting_animation_scale
;
497 void TransformTree::UndoSnapping(TransformNode
* node
) {
498 // to_parent transform has the scroll snap from previous frame baked in.
499 // We need to undo it and use the un-snapped transform to compute current
500 // target and screen space transforms.
501 node
->data
.to_parent
.Translate(-node
->data
.scroll_snap
.x(),
502 -node
->data
.scroll_snap
.y());
505 void TransformTree::UpdateSnapping(TransformNode
* node
) {
506 if (!node
->data
.scrolls
|| node
->data
.to_screen_is_animated
||
507 !node
->data
.to_target
.IsScaleOrTranslation()) {
511 // Scroll snapping must be done in target space (the pixels we care about).
512 // This means we effectively snap the target space transform. If TT is the
513 // target space transform and TT' is TT with its translation components
514 // rounded, then what we're after is the scroll delta X, where TT * X = TT'.
515 // I.e., we want a transform that will realize our scroll snap. It follows
516 // that X = TT^-1 * TT'. We cache TT and TT^-1 to make this more efficient.
517 gfx::Transform rounded
= node
->data
.to_target
;
518 rounded
.RoundTranslationComponents();
519 gfx::Transform delta
= node
->data
.from_target
;
522 DCHECK(delta
.IsApproximatelyIdentityOrTranslation(SkDoubleToMScalar(1e-4)))
525 gfx::Vector2dF translation
= delta
.To2dTranslation();
527 // Now that we have our scroll delta, we must apply it to each of our
528 // combined, to/from matrices.
529 node
->data
.to_target
= rounded
;
530 node
->data
.to_parent
.Translate(translation
.x(), translation
.y());
531 node
->data
.from_target
.matrix().postTranslate(-translation
.x(),
532 -translation
.y(), 0);
533 node
->data
.to_screen
.Translate(translation
.x(), translation
.y());
534 node
->data
.from_screen
.matrix().postTranslate(-translation
.x(),
535 -translation
.y(), 0);
537 node
->data
.scroll_snap
= translation
;
540 void TransformTree::SetInnerViewportBoundsDelta(gfx::Vector2dF bounds_delta
) {
541 if (inner_viewport_bounds_delta_
== bounds_delta
)
544 inner_viewport_bounds_delta_
= bounds_delta
;
546 if (nodes_affected_by_inner_viewport_bounds_delta_
.empty())
549 set_needs_update(true);
550 for (int i
: nodes_affected_by_inner_viewport_bounds_delta_
)
551 Node(i
)->data
.needs_local_transform_update
= true;
554 void TransformTree::SetOuterViewportBoundsDelta(gfx::Vector2dF bounds_delta
) {
555 if (outer_viewport_bounds_delta_
== bounds_delta
)
558 outer_viewport_bounds_delta_
= bounds_delta
;
560 if (nodes_affected_by_outer_viewport_bounds_delta_
.empty())
563 set_needs_update(true);
564 for (int i
: nodes_affected_by_outer_viewport_bounds_delta_
)
565 Node(i
)->data
.needs_local_transform_update
= true;
568 void TransformTree::AddNodeAffectedByInnerViewportBoundsDelta(int node_id
) {
569 nodes_affected_by_inner_viewport_bounds_delta_
.push_back(node_id
);
572 void TransformTree::AddNodeAffectedByOuterViewportBoundsDelta(int node_id
) {
573 nodes_affected_by_outer_viewport_bounds_delta_
.push_back(node_id
);
576 bool TransformTree::HasNodesAffectedByInnerViewportBoundsDelta() const {
577 return !nodes_affected_by_inner_viewport_bounds_delta_
.empty();
580 bool TransformTree::HasNodesAffectedByOuterViewportBoundsDelta() const {
581 return !nodes_affected_by_outer_viewport_bounds_delta_
.empty();
584 void EffectTree::UpdateOpacities(int id
) {
585 EffectNode
* node
= Node(id
);
586 node
->data
.screen_space_opacity
= node
->data
.opacity
;
588 EffectNode
* parent_node
= parent(node
);
590 node
->data
.screen_space_opacity
*= parent_node
->data
.screen_space_opacity
;
593 void TransformTree::UpdateNodeAndAncestorsHaveIntegerTranslations(
595 TransformNode
* parent_node
) {
596 node
->data
.node_and_ancestors_have_only_integer_translation
=
597 node
->data
.to_parent
.IsIdentityOrIntegerTranslation();
599 node
->data
.node_and_ancestors_have_only_integer_translation
=
600 node
->data
.node_and_ancestors_have_only_integer_translation
&&
601 parent_node
->data
.node_and_ancestors_have_only_integer_translation
;
604 void ClipTree::SetViewportClip(gfx::RectF viewport_rect
) {
607 ClipNode
* node
= Node(1);
608 if (viewport_rect
== node
->data
.clip
)
610 node
->data
.clip
= viewport_rect
;
611 set_needs_update(true);
614 gfx::RectF
ClipTree::ViewportClip() {
615 const unsigned long min_size
= 1;
616 DCHECK_GT(size(), min_size
);
617 return Node(1)->data
.clip
;
620 PropertyTrees::PropertyTrees() : needs_rebuild(true), sequence_number(0) {