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