Chromium Code Reviews| OLD | NEW |
|---|---|
| 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/dom/DOMNodeIds.h" | 7 #include "core/dom/DOMNodeIds.h" |
| 8 #include "core/frame/FrameView.h" | 8 #include "core/frame/FrameView.h" |
| 9 #include "core/frame/LocalFrame.h" | 9 #include "core/frame/LocalFrame.h" |
| 10 #include "core/frame/Settings.h" | 10 #include "core/frame/Settings.h" |
| (...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 408 context.current.renderingContextId = | 408 context.current.renderingContextId = |
| 409 properties->transform()->renderingContextId(); | 409 properties->transform()->renderingContextId(); |
| 410 context.current.shouldFlattenInheritedTransform = false; | 410 context.current.shouldFlattenInheritedTransform = false; |
| 411 } else { | 411 } else { |
| 412 context.current.renderingContextId = 0; | 412 context.current.renderingContextId = 0; |
| 413 context.current.shouldFlattenInheritedTransform = true; | 413 context.current.shouldFlattenInheritedTransform = true; |
| 414 } | 414 } |
| 415 } | 415 } |
| 416 } | 416 } |
| 417 | 417 |
| 418 static bool computeMaskParameters(IntRect& maskClip, | |
|
chrishtr
2017/02/11 23:55:11
The bounds of the mask requires the bounds of chil
trchen
2017/02/13 21:12:02
Only for SVG. For HTML/CSS we only use the current
| |
| 419 const LayoutObject& object, | |
| 420 const LayoutPoint& paintOffset) { | |
| 421 DCHECK(object.isBoxModelObject() || object.isSVGChild()); | |
| 422 const ComputedStyle& style = object.styleRef(); | |
| 423 | |
| 424 if (object.isSVGChild()) { | |
| 425 // TODO(trchen): Figure out what to do with SVG. SVG may use object | |
|
chrishtr
2017/02/13 23:23:49
Remove this comment, per discussion.
trchen
2017/02/14 01:37:30
Alright. Changed it to just "Implement SVG masks."
| |
| 426 // bounding box for mask sizing, but computing it requires property | |
| 427 // tree to be built. | |
| 428 return false; | |
| 429 } | |
| 430 if (!style.hasMask()) | |
| 431 return false; | |
| 432 | |
| 433 LayoutRect maximumMaskRegion; | |
| 434 // For HTML/CSS objects, the extent of the mask is known as "mask | |
| 435 // painting area", which is determined by CSS mask-clip property. | |
| 436 // We don't implement mask-clip:margin-box or no-clip currently, | |
| 437 // so the maximum we can get is border-box. | |
| 438 if (object.isBox()) { | |
| 439 maximumMaskRegion = toLayoutBox(object).borderBoxRect(); | |
| 440 } else { | |
| 441 // For inline elements, depends on the value of box-decoration-break | |
| 442 // there could be one box in multiple fragments or multiple boxes. | |
| 443 // Either way here we are only interested in the bounding box of them. | |
| 444 DCHECK(object.isLayoutInline()); | |
| 445 maximumMaskRegion = toLayoutInline(object).linesBoundingBox(); | |
|
chrishtr
2017/02/13 23:23:49
Can you point me to the legacy code which implemen
trchen
2017/02/14 01:37:30
The legacy code doesn't clip inline overflow corre
chrishtr
2017/02/14 02:11:33
I wonder if this has been filed as a bug before. D
trchen
2017/02/14 02:22:04
I'm not aware of one either.
| |
| 446 } | |
| 447 maximumMaskRegion.moveBy(paintOffset); | |
| 448 maskClip = enclosingIntRect(maximumMaskRegion); | |
| 449 return true; | |
| 450 } | |
| 451 | |
| 418 void PaintPropertyTreeBuilder::updateEffect( | 452 void PaintPropertyTreeBuilder::updateEffect( |
| 419 const LayoutObject& object, | 453 const LayoutObject& object, |
| 420 PaintPropertyTreeBuilderContext& context) { | 454 PaintPropertyTreeBuilderContext& context) { |
| 421 const ComputedStyle& style = object.styleRef(); | 455 const ComputedStyle& style = object.styleRef(); |
| 422 | 456 |
| 423 const bool isCSSIsolatedGroup = | 457 const bool isCSSIsolatedGroup = |
| 424 object.isBoxModelObject() && style.isStackingContext(); | 458 object.isBoxModelObject() && style.isStackingContext(); |
| 425 if (!isCSSIsolatedGroup && !object.isSVGChild()) { | 459 if (!isCSSIsolatedGroup && !object.isSVGChild()) { |
| 426 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { | 460 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { |
| 427 if (auto* properties = object.getMutableForPainting().paintProperties()) | 461 if (auto* properties = object.getMutableForPainting().paintProperties()) { |
| 428 context.forceSubtreeUpdate |= properties->clearEffect(); | 462 context.forceSubtreeUpdate |= properties->clearEffect(); |
| 463 context.forceSubtreeUpdate |= properties->clearMask(); | |
| 464 context.forceSubtreeUpdate |= properties->clearMaskClip(); | |
| 465 } | |
| 429 } | 466 } |
| 430 return; | 467 return; |
| 431 } | 468 } |
| 432 | 469 |
| 433 // TODO(trchen): Can't omit effect node if we have 3D children. | 470 // TODO(trchen): Can't omit effect node if we have 3D children. |
| 434 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { | 471 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { |
| 472 const ClipPaintPropertyNode* outputClip = context.inputClipOfCurrentEffect; | |
| 473 | |
| 435 bool effectNodeNeeded = false; | 474 bool effectNodeNeeded = false; |
| 436 | 475 |
| 437 // Can't omit effect node if we have paint children with exotic blending. | 476 // Can't omit effect node if we have paint children with exotic blending. |
| 438 if (object.isSVG()) { | 477 if (object.isSVG()) { |
| 439 // Yes, including LayoutSVGRoot, because SVG layout objects don't create | 478 // Yes, including LayoutSVGRoot, because SVG layout objects don't create |
| 440 // PaintLayer so PaintLayer::hasNonIsolatedDescendantWithBlendMode() | 479 // PaintLayer so PaintLayer::hasNonIsolatedDescendantWithBlendMode() |
| 441 // doesn't catch SVG descendants. | 480 // doesn't catch SVG descendants. |
| 442 if (SVGLayoutSupport::isIsolationRequired(&object)) | 481 if (SVGLayoutSupport::isIsolationRequired(&object)) |
| 443 effectNodeNeeded = true; | 482 effectNodeNeeded = true; |
| 444 } else if (PaintLayer* layer = toLayoutBoxModelObject(object).layer()) { | 483 } else if (PaintLayer* layer = toLayoutBoxModelObject(object).layer()) { |
| 445 if (layer->hasNonIsolatedDescendantWithBlendMode()) | 484 if (layer->hasNonIsolatedDescendantWithBlendMode()) |
| 446 effectNodeNeeded = true; | 485 effectNodeNeeded = true; |
| 447 } | 486 } |
| 448 | 487 |
| 449 SkBlendMode blendMode = object.isBlendingAllowed() | 488 SkBlendMode blendMode = object.isBlendingAllowed() |
| 450 ? WebCoreCompositeToSkiaComposite( | 489 ? WebCoreCompositeToSkiaComposite( |
| 451 CompositeSourceOver, style.blendMode()) | 490 CompositeSourceOver, style.blendMode()) |
| 452 : SkBlendMode::kSrcOver; | 491 : SkBlendMode::kSrcOver; |
| 453 if (blendMode != SkBlendMode::kSrcOver) | 492 if (blendMode != SkBlendMode::kSrcOver) |
| 454 effectNodeNeeded = true; | 493 effectNodeNeeded = true; |
| 455 | 494 |
| 456 float opacity = style.opacity(); | 495 float opacity = style.opacity(); |
| 457 if (opacity != 1.0f) | 496 if (opacity != 1.0f) |
| 458 effectNodeNeeded = true; | 497 effectNodeNeeded = true; |
| 459 | 498 |
| 499 // We may begin to composite our subtree prior to an animation starts, | |
| 500 // but a compositor element ID is only needed when an animation is current. | |
| 501 CompositorElementId compositorElementId = | |
| 502 style.hasCurrentOpacityAnimation() | |
| 503 ? createDomNodeBasedCompositorElementId(object) | |
| 504 : CompositorElementId(); | |
| 505 CompositingReasons compositingReasons = CompositingReasonNone; | |
| 506 if (CompositingReasonFinder::requiresCompositingForOpacityAnimation( | |
| 507 style)) { | |
| 508 compositingReasons = CompositingReasonActiveAnimation; | |
| 509 effectNodeNeeded = true; | |
| 510 } | |
| 511 DCHECK(!style.hasCurrentOpacityAnimation() || | |
| 512 compositingReasons != CompositingReasonNone); | |
| 513 | |
| 514 IntRect maskClip; | |
| 515 bool hasMask = | |
| 516 computeMaskParameters(maskClip, object, context.current.paintOffset); | |
| 517 if (hasMask) { | |
| 518 effectNodeNeeded = true; | |
| 519 | |
| 520 auto& properties = object.getMutableForPainting().ensurePaintProperties(); | |
| 521 context.forceSubtreeUpdate |= properties.updateMaskClip( | |
| 522 context.current.clip, context.current.transform, | |
| 523 FloatRoundedRect(maskClip)); | |
| 524 outputClip = properties.maskClip(); | |
| 525 | |
| 526 // TODO(crbug.com/683425): PaintArtifactCompositor does not handle | |
| 527 // grouping (i.e. descendant-dependent compositing reason) properly yet. | |
| 528 // This forces masked subtree always create a layer for now. | |
| 529 compositingReasons |= CompositingReasonIsolateCompositedDescendants; | |
| 530 } else { | |
| 531 if (auto* properties = object.getMutableForPainting().paintProperties()) | |
| 532 context.forceSubtreeUpdate |= properties->clearMaskClip(); | |
| 533 } | |
| 534 | |
| 535 if (effectNodeNeeded) { | |
| 536 auto& properties = object.getMutableForPainting().ensurePaintProperties(); | |
| 537 context.forceSubtreeUpdate |= properties.updateEffect( | |
| 538 context.currentEffect, context.current.transform, outputClip, | |
| 539 CompositorFilterOperations(), opacity, blendMode, compositingReasons, | |
| 540 compositorElementId); | |
| 541 if (hasMask) { | |
| 542 // TODO(crbug.com/683425): PaintArtifactCompositor does not handle | |
|
chrishtr
2017/02/13 23:23:49
Please adjust the comment to make it clear that th
trchen
2017/02/14 01:37:30
Done.
| |
| 543 // grouping (i.e. descendant-dependent compositing reason) properly yet. | |
| 544 // This forces mask won't get squashed into a child effect. | |
| 545 context.forceSubtreeUpdate |= properties.updateMask( | |
| 546 properties.effect(), context.current.transform, outputClip, | |
| 547 CompositorFilterOperations(), 1.f, SkBlendMode::kDstIn, | |
| 548 CompositingReasonSquashingDisallowed, CompositorElementId()); | |
| 549 } else { | |
| 550 context.forceSubtreeUpdate |= properties.clearMask(); | |
| 551 } | |
| 552 } else { | |
| 553 if (auto* properties = object.getMutableForPainting().paintProperties()) { | |
| 554 context.forceSubtreeUpdate |= properties->clearEffect(); | |
| 555 context.forceSubtreeUpdate |= properties->clearMask(); | |
| 556 } | |
| 557 } | |
| 558 } | |
| 559 | |
| 560 const auto* properties = object.paintProperties(); | |
| 561 if (properties && properties->effect()) { | |
| 562 context.currentEffect = properties->effect(); | |
| 563 if (properties->maskClip()) { | |
| 564 context.inputClipOfCurrentEffect = context.current.clip = | |
| 565 context.absolutePosition.clip = context.fixedPosition.clip = | |
| 566 properties->maskClip(); | |
| 567 } | |
| 568 } | |
| 569 } | |
| 570 | |
| 571 void PaintPropertyTreeBuilder::updateFilter( | |
| 572 const LayoutObject& object, | |
| 573 PaintPropertyTreeBuilderContext& context) { | |
| 574 const ComputedStyle& style = object.styleRef(); | |
| 575 | |
| 576 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { | |
| 460 CompositorFilterOperations filter; | 577 CompositorFilterOperations filter; |
| 461 if (object.isSVGChild()) { | 578 if (object.isSVGChild()) { |
| 462 // TODO(trchen): SVG caches filters in SVGResources. Implement it. | 579 // TODO(trchen): SVG caches filters in SVGResources. Implement it. |
| 463 } else if (PaintLayer* layer = toLayoutBoxModelObject(object).layer()) { | 580 } else if (PaintLayer* layer = toLayoutBoxModelObject(object).layer()) { |
| 464 // TODO(trchen): Eliminate PaintLayer dependency. | 581 // TODO(trchen): Eliminate PaintLayer dependency. |
| 465 filter = layer->createCompositorFilterOperationsForFilter(style); | 582 filter = layer->createCompositorFilterOperationsForFilter(style); |
| 466 } | 583 } |
| 467 | 584 |
| 468 const ClipPaintPropertyNode* outputClip = context.inputClipOfCurrentEffect; | |
| 469 // The CSS filter spec didn't specify how filters interact with overflow | 585 // The CSS filter spec didn't specify how filters interact with overflow |
| 470 // clips. The implementation here mimics the old Blink/WebKit behavior for | 586 // clips. The implementation here mimics the old Blink/WebKit behavior for |
| 471 // backward compatibility. | 587 // backward compatibility. |
| 472 // Basically the output of the filter will be affected by clips that applies | 588 // Basically the output of the filter will be affected by clips that applies |
| 473 // to the current element. The descendants that paints into the input of the | 589 // to the current element. The descendants that paints into the input of the |
| 474 // filter ignores any clips collected so far. For example: | 590 // filter ignores any clips collected so far. For example: |
| 475 // <div style="overflow:scroll"> | 591 // <div style="overflow:scroll"> |
| 476 // <div style="filter:blur(1px);"> | 592 // <div style="filter:blur(1px);"> |
| 477 // <div>A</div> | 593 // <div>A</div> |
| 478 // <div style="position:absolute;">B</div> | 594 // <div style="position:absolute;">B</div> |
| 479 // </div> | 595 // </div> |
| 480 // </div> | 596 // </div> |
| 481 // In this example "A" should be clipped if the filter was not present. | 597 // In this example "A" should be clipped if the filter was not present. |
| 482 // With the filter, "A" will be rastered without clipping, but instead | 598 // With the filter, "A" will be rastered without clipping, but instead |
| 483 // the blurred result will be clipped. | 599 // the blurred result will be clipped. |
| 484 // On the other hand, "B" should not be clipped because the overflow clip is | 600 // On the other hand, "B" should not be clipped because the overflow clip is |
| 485 // not in its containing block chain, but as the filter output will be | 601 // not in its containing block chain, but as the filter output will be |
| 486 // clipped, so a blurred "B" may still be invisible. | 602 // clipped, so a blurred "B" may still be invisible. |
| 487 if (!filter.isEmpty()) { | 603 const ClipPaintPropertyNode* outputClip = context.current.clip; |
| 488 effectNodeNeeded = true; | |
| 489 outputClip = context.current.clip; | |
| 490 | 604 |
| 491 // TODO(trchen): A filter may contain spatial operations such that an | 605 // TODO(trchen): A filter may contain spatial operations such that an |
| 492 // output pixel may depend on an input pixel outside of the output clip. | 606 // output pixel may depend on an input pixel outside of the output clip. |
| 493 // We should generate a special clip node to represent this expansion. | 607 // We should generate a special clip node to represent this expansion. |
| 494 } | |
| 495 | 608 |
| 496 CompositingReasons compositingReasons = CompositingReasonNone; | 609 // We may begin to composite our subtree prior to an animation starts, |
| 497 if (CompositingReasonFinder::requiresCompositingForEffectAnimation(style)) { | 610 // but a compositor element ID is only needed when an animation is current. |
| 498 compositingReasons = CompositingReasonActiveAnimation; | |
| 499 effectNodeNeeded = true; | |
| 500 } | |
| 501 | |
| 502 CompositorElementId compositorElementId = | 611 CompositorElementId compositorElementId = |
| 503 (style.hasCurrentOpacityAnimation() || | 612 style.hasCurrentFilterAnimation() |
| 504 style.hasCurrentFilterAnimation() || | |
| 505 style.hasCurrentBackdropFilterAnimation()) | |
| 506 ? createDomNodeBasedCompositorElementId(object) | 613 ? createDomNodeBasedCompositorElementId(object) |
| 507 : CompositorElementId(); | 614 : CompositorElementId(); |
| 615 CompositingReasons compositingReasons = | |
| 616 CompositingReasonFinder::requiresCompositingForFilterAnimation(style) | |
| 617 ? CompositingReasonActiveAnimation | |
| 618 : CompositingReasonNone; | |
| 619 DCHECK(!style.hasCurrentFilterAnimation() || | |
| 620 compositingReasons != CompositingReasonNone); | |
| 508 | 621 |
| 509 if (effectNodeNeeded) { | 622 if (compositingReasons == CompositingReasonNone && filter.isEmpty()) { |
| 623 if (auto* properties = object.getMutableForPainting().paintProperties()) | |
| 624 context.forceSubtreeUpdate |= properties->clearFilter(); | |
| 625 } else { | |
| 510 auto& properties = object.getMutableForPainting().ensurePaintProperties(); | 626 auto& properties = object.getMutableForPainting().ensurePaintProperties(); |
| 511 context.forceSubtreeUpdate |= properties.updateEffect( | 627 context.forceSubtreeUpdate |= properties.updateFilter( |
| 512 context.currentEffect, context.current.transform, outputClip, | 628 context.currentEffect, context.current.transform, outputClip, |
| 513 std::move(filter), opacity, blendMode, compositingReasons, | 629 std::move(filter), 1.f, SkBlendMode::kSrcOver, compositingReasons, |
| 514 compositorElementId); | 630 compositorElementId); |
| 515 } else { | |
| 516 if (auto* properties = object.getMutableForPainting().paintProperties()) | |
| 517 context.forceSubtreeUpdate |= properties->clearEffect(); | |
| 518 } | 631 } |
| 519 } | 632 } |
| 520 | 633 |
| 521 const auto* properties = object.paintProperties(); | 634 const auto* properties = object.paintProperties(); |
| 522 if (properties && properties->effect()) { | 635 if (properties && properties->filter()) { |
| 523 context.currentEffect = properties->effect(); | 636 context.currentEffect = properties->filter(); |
| 524 if (!properties->effect()->filter().isEmpty()) { | 637 // TODO(trchen): Change input clip to expansion hint once implemented. |
| 525 // TODO(trchen): Change input clip to expansion hint once implemented. | 638 const ClipPaintPropertyNode* inputClip = properties->filter()->outputClip(); |
| 526 const ClipPaintPropertyNode* inputClip = | 639 context.inputClipOfCurrentEffect = context.current.clip = |
| 527 properties->effect()->outputClip(); | 640 context.absolutePosition.clip = context.fixedPosition.clip = inputClip; |
| 528 context.inputClipOfCurrentEffect = context.current.clip = | |
| 529 context.absolutePosition.clip = context.fixedPosition.clip = | |
| 530 inputClip; | |
| 531 } | |
| 532 } | 641 } |
| 533 } | 642 } |
| 534 | 643 |
| 535 void PaintPropertyTreeBuilder::updateCssClip( | 644 void PaintPropertyTreeBuilder::updateCssClip( |
| 536 const LayoutObject& object, | 645 const LayoutObject& object, |
| 537 PaintPropertyTreeBuilderContext& context) { | 646 PaintPropertyTreeBuilderContext& context) { |
| 538 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { | 647 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { |
| 539 if (object.hasClip()) { | 648 if (object.hasClip()) { |
| 540 // Create clip node for descendants that are not fixed position. | 649 // Create clip node for descendants that are not fixed position. |
| 541 // We don't have to setup context.absolutePosition.clip here because this | 650 // We don't have to setup context.absolutePosition.clip here because this |
| (...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 939 void PaintPropertyTreeBuilder::updatePropertiesForSelf( | 1048 void PaintPropertyTreeBuilder::updatePropertiesForSelf( |
| 940 const LayoutObject& object, | 1049 const LayoutObject& object, |
| 941 PaintPropertyTreeBuilderContext& context) { | 1050 PaintPropertyTreeBuilderContext& context) { |
| 942 #if DCHECK_IS_ON() | 1051 #if DCHECK_IS_ON() |
| 943 FindObjectPropertiesNeedingUpdateScope checkNeedsUpdateScope(object, context); | 1052 FindObjectPropertiesNeedingUpdateScope checkNeedsUpdateScope(object, context); |
| 944 #endif | 1053 #endif |
| 945 | 1054 |
| 946 if (object.isBoxModelObject() || object.isSVG()) { | 1055 if (object.isBoxModelObject() || object.isSVG()) { |
| 947 updatePaintOffsetTranslation(object, context); | 1056 updatePaintOffsetTranslation(object, context); |
| 948 updateTransform(object, context); | 1057 updateTransform(object, context); |
| 949 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) | 1058 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| 950 updateEffect(object, context); | 1059 updateEffect(object, context); |
| 1060 updateFilter(object, context); | |
| 1061 } | |
| 951 updateCssClip(object, context); | 1062 updateCssClip(object, context); |
| 952 updateLocalBorderBoxContext(object, context); | 1063 updateLocalBorderBoxContext(object, context); |
| 953 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) | 1064 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| 954 updateScrollbarPaintOffset(object, context); | 1065 updateScrollbarPaintOffset(object, context); |
| 955 } | 1066 } |
| 956 | 1067 |
| 957 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { | 1068 if (object.needsPaintPropertyUpdate() || context.forceSubtreeUpdate) { |
| 958 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && | 1069 if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && |
| 959 object.paintOffset() != context.current.paintOffset) { | 1070 object.paintOffset() != context.current.paintOffset) { |
| 960 object.getMutableForPainting().setShouldDoFullPaintInvalidation( | 1071 object.getMutableForPainting().setShouldDoFullPaintInvalidation( |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 977 updateOverflowClip(object, context); | 1088 updateOverflowClip(object, context); |
| 978 updatePerspective(object, context); | 1089 updatePerspective(object, context); |
| 979 updateSvgLocalToBorderBoxTransform(object, context); | 1090 updateSvgLocalToBorderBoxTransform(object, context); |
| 980 updateScrollAndScrollTranslation(object, context); | 1091 updateScrollAndScrollTranslation(object, context); |
| 981 updateOutOfFlowContext(object, context); | 1092 updateOutOfFlowContext(object, context); |
| 982 | 1093 |
| 983 context.forceSubtreeUpdate |= object.subtreeNeedsPaintPropertyUpdate(); | 1094 context.forceSubtreeUpdate |= object.subtreeNeedsPaintPropertyUpdate(); |
| 984 } | 1095 } |
| 985 | 1096 |
| 986 } // namespace blink | 1097 } // namespace blink |
| OLD | NEW |