blob: c3c6fd4027c722611ab1d47b84816ba70d69f3d4 [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cycle_solver.h"
#include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
#include "third_party/blink/renderer/core/svg/svg_resource.h"
#include "third_party/blink/renderer/core/svg/svg_tree_scope_resources.h"
namespace blink {
namespace {
LocalSVGResource* ResourceForContainer(
const LayoutSVGResourceContainer& resource_container) {
const SVGElement& element = *resource_container.GetElement();
return element.GetTreeScope()
.EnsureSVGTreeScopedResources()
.ExistingResourceForId(element.GetIdAttribute());
}
} // namespace
LayoutSVGResourceContainer::LayoutSVGResourceContainer(SVGElement* node)
: LayoutSVGHiddenContainer(node),
completed_invalidations_mask_(0),
is_invalidating_(false) {}
LayoutSVGResourceContainer::~LayoutSVGResourceContainer() = default;
void LayoutSVGResourceContainer::UpdateLayout() {
NOT_DESTROYED();
// TODO(fs): This is only here to clear the invalidation mask, without that
// we wouldn't need to override LayoutSVGHiddenContainer::UpdateLayout().
DCHECK(NeedsLayout());
LayoutSVGHiddenContainer::UpdateLayout();
ClearInvalidationMask();
}
bool LayoutSVGResourceContainer::FindCycle(
SVGResourcesCycleSolver& solver) const {
NOT_DESTROYED();
if (solver.IsKnownAcyclic(this))
return false;
SVGResourcesCycleSolver::Scope scope(solver);
if (!scope.Enter(this) || FindCycleFromSelf(solver))
return true;
solver.AddAcyclicSubgraph(this);
return false;
}
static HeapVector<Member<SVGResource>> CollectResources(
const LayoutObject& layout_object) {
const ComputedStyle& style = layout_object.StyleRef();
HeapVector<Member<SVGResource>> resources;
if (auto* reference_clip =
DynamicTo<ReferenceClipPathOperation>(style.ClipPath())) {
resources.push_back(reference_clip->Resource());
}
for (const auto& operation : style.Filter().Operations()) {
if (auto* reference_operation =
DynamicTo<ReferenceFilterOperation>(*operation))
resources.push_back(reference_operation->Resource());
}
const SVGComputedStyle& svg_style = style.SvgStyle();
if (auto* masker = svg_style.MaskerResource())
resources.push_back(masker->Resource());
if (auto* marker = svg_style.MarkerStartResource())
resources.push_back(marker->Resource());
if (auto* marker = svg_style.MarkerMidResource())
resources.push_back(marker->Resource());
if (auto* marker = svg_style.MarkerEndResource())
resources.push_back(marker->Resource());
if (auto* paint_resource = svg_style.FillPaint().Resource())
resources.push_back(paint_resource->Resource());
if (auto* paint_resource = svg_style.StrokePaint().Resource())
resources.push_back(paint_resource->Resource());
return resources;
}
bool LayoutSVGResourceContainer::FindCycleInResources(
SVGResourcesCycleSolver& solver,
const LayoutObject& layout_object) {
if (!layout_object.IsSVG() || layout_object.IsText())
return false;
SVGResourceClient* client = SVGResources::GetClient(layout_object);
// Without an associated client, we will not reference any resources.
if (!client)
return false;
// Fetch all the referenced resources.
HeapVector<Member<SVGResource>> resources = CollectResources(layout_object);
// This performs a depth-first search for a back-edge in all the
// (potentially disjoint) graphs formed by the referenced resources.
for (const auto& local_resource : resources) {
// The resource can be null if the reference is external but external
// references are not allowed.
if (local_resource && local_resource->FindCycle(*client, solver))
return true;
}
return false;
}
bool LayoutSVGResourceContainer::FindCycleFromSelf(
SVGResourcesCycleSolver& solver) const {
NOT_DESTROYED();
// Resources don't generally apply to other resources, so require
// the specific cases that do (like <clipPath>) to implement an
// override.
return FindCycleInDescendants(solver, *this);
}
bool LayoutSVGResourceContainer::FindCycleInDescendants(
SVGResourcesCycleSolver& solver,
const LayoutObject& root) {
LayoutObject* node = root.SlowFirstChild();
while (node) {
// Skip subtrees which are themselves resources. (They will be
// processed - if needed - when they are actually referenced.)
if (node->IsSVGResourceContainer()) {
node = node->NextInPreOrderAfterChildren(&root);
continue;
}
if (FindCycleInResources(solver, *node))
return true;
node = node->NextInPreOrder(&root);
}
return false;
}
bool LayoutSVGResourceContainer::FindCycleInSubtree(
SVGResourcesCycleSolver& solver,
const LayoutObject& root) {
if (FindCycleInResources(solver, root))
return true;
return FindCycleInDescendants(solver, root);
}
void LayoutSVGResourceContainer::MarkAllClientsForInvalidation(
InvalidationModeMask invalidation_mask) {
NOT_DESTROYED();
if (is_invalidating_)
return;
LocalSVGResource* resource = ResourceForContainer(*this);
if (!resource)
return;
// Remove modes for which invalidations have already been
// performed. If no modes remain we are done.
invalidation_mask &= ~completed_invalidations_mask_;
if (invalidation_mask == 0)
return;
completed_invalidations_mask_ |= invalidation_mask;
is_invalidating_ = true;
// Invalidate clients registered via an SVGResource.
resource->NotifyContentChanged(invalidation_mask);
is_invalidating_ = false;
}
void LayoutSVGResourceContainer::InvalidateCacheAndMarkForLayout(
LayoutInvalidationReasonForTracing reason,
SubtreeLayoutScope* layout_scope) {
NOT_DESTROYED();
if (SelfNeedsLayout())
return;
SetNeedsLayoutAndFullPaintInvalidation(reason, kMarkContainerChain,
layout_scope);
if (EverHadLayout())
RemoveAllClientsFromCache();
}
void LayoutSVGResourceContainer::InvalidateCacheAndMarkForLayout(
SubtreeLayoutScope* layout_scope) {
NOT_DESTROYED();
InvalidateCacheAndMarkForLayout(
layout_invalidation_reason::kSvgResourceInvalidated, layout_scope);
}
static inline void RemoveFromCacheAndInvalidateDependencies(
LayoutObject& object,
bool needs_layout) {
auto* element = DynamicTo<SVGElement>(object.GetNode());
if (!element)
return;
// TODO(fs): Do we still need this? (If bounds are invalidated on a leaf
// LayoutObject, we will propagate that during the required layout and
// invalidate effects of self and any ancestors at that time.)
SVGResourceInvalidator(object).InvalidateEffects();
element->NotifyIncomingReferences([needs_layout](SVGElement& element) {
DCHECK(element.GetLayoutObject());
LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
*element.GetLayoutObject(), needs_layout);
});
}
void LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
LayoutObject& object,
bool needs_layout) {
DCHECK(object.GetNode());
if (needs_layout && !object.DocumentBeingDestroyed()) {
object.SetNeedsLayoutAndFullPaintInvalidation(
layout_invalidation_reason::kSvgResourceInvalidated);
}
RemoveFromCacheAndInvalidateDependencies(object, needs_layout);
// Invalidate resources in ancestor chain, if needed.
LayoutObject* current = object.Parent();
while (current) {
RemoveFromCacheAndInvalidateDependencies(*current, needs_layout);
if (current->IsSVGResourceContainer()) {
// This will process the rest of the ancestors.
To<LayoutSVGResourceContainer>(current)->RemoveAllClientsFromCache();
break;
}
current = current->Parent();
}
}
static inline bool IsLayoutObjectOfResourceContainer(
const LayoutObject& layout_object) {
const LayoutObject* current = &layout_object;
while (current) {
if (current->IsSVGResourceContainer())
return true;
current = current->Parent();
}
return false;
}
void LayoutSVGResourceContainer::StyleChanged(LayoutObject& object,
StyleDifference diff) {
// If this LayoutObject is the child of a resource container and
// it requires repainting because of changes to CSS properties
// such as 'visibility', upgrade to invalidate layout.
bool needs_layout = diff.NeedsPaintInvalidation() &&
IsLayoutObjectOfResourceContainer(object);
MarkForLayoutAndParentResourceInvalidation(object, needs_layout);
}
} // namespace blink