blob: 71b6cd322c0050f762961253c347b216ed77cf02 [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 "core/layout/svg/LayoutSVGResourceContainer.h"
#include "core/layout/svg/SVGResources.h"
#include "core/layout/svg/SVGResourcesCache.h"
#include "core/svg/SVGElementProxy.h"
#include "core/svg/SVGTreeScopeResources.h"
#include "platform/wtf/AutoReset.h"
namespace blink {
static inline SVGTreeScopeResources& SvgTreeScopeResourcesFromElement(
Element* element) {
DCHECK(element);
return element->GetTreeScope().EnsureSVGTreeScopedResources();
}
LayoutSVGResourceContainer::LayoutSVGResourceContainer(SVGElement* node)
: LayoutSVGHiddenContainer(node),
is_in_layout_(false),
invalidation_mask_(0),
registered_(false),
is_invalidating_(false) {}
LayoutSVGResourceContainer::~LayoutSVGResourceContainer() = default;
void LayoutSVGResourceContainer::UpdateLayout() {
// FIXME: Investigate a way to detect and break resource layout dependency
// cycles early. Then we can remove this method altogether, and fall back onto
// LayoutSVGHiddenContainer::layout().
DCHECK(NeedsLayout());
if (is_in_layout_)
return;
AutoReset<bool> in_layout_change(&is_in_layout_, true);
LayoutSVGHiddenContainer::UpdateLayout();
ClearInvalidationMask();
}
SVGElementProxySet* LayoutSVGResourceContainer::ElementProxySet() {
return GetElement()->ElementProxySet();
}
void LayoutSVGResourceContainer::NotifyContentChanged() {
if (SVGElementProxySet* proxy_set = ElementProxySet())
proxy_set->NotifyContentChanged(GetElement()->GetTreeScope());
}
void LayoutSVGResourceContainer::WillBeDestroyed() {
LayoutSVGHiddenContainer::WillBeDestroyed();
SvgTreeScopeResourcesFromElement(GetElement())
.RemoveResource(GetElement()->GetIdAttribute(), this);
DCHECK(clients_.IsEmpty());
}
void LayoutSVGResourceContainer::StyleDidChange(
StyleDifference diff,
const ComputedStyle* old_style) {
LayoutSVGHiddenContainer::StyleDidChange(diff, old_style);
SvgTreeScopeResourcesFromElement(GetElement())
.UpdateResource(GetElement()->GetIdAttribute(), this);
}
void LayoutSVGResourceContainer::DetachAllClients(const AtomicString& to_id) {
RemoveAllClientsFromCache();
for (auto* client : clients_) {
// Unlink the resource from the client's SVGResources. (The actual
// removal will be signaled after processing all the clients.)
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(client);
// Or else the client wouldn't be in the list in the first place.
DCHECK(resources);
resources->ResourceDestroyed(this);
// Add a pending resolution based on the id of the old resource.
Element* client_element = ToElement(client->GetNode());
SvgTreeScopeResourcesFromElement(client_element)
.AddPendingResource(to_id, *client_element);
}
clients_.clear();
}
void LayoutSVGResourceContainer::IdChanged(const AtomicString& old_id,
const AtomicString& new_id) {
SvgTreeScopeResourcesFromElement(GetElement())
.UpdateResource(old_id, new_id, this);
}
void LayoutSVGResourceContainer::MarkAllClientsForInvalidation(
InvalidationMode mode) {
if (is_invalidating_)
return;
SVGElementProxySet* proxy_set = ElementProxySet();
if (clients_.IsEmpty() && (!proxy_set || proxy_set->IsEmpty()))
return;
if (invalidation_mask_ & mode)
return;
invalidation_mask_ |= mode;
is_invalidating_ = true;
bool needs_layout = mode == kLayoutAndBoundariesInvalidation;
bool mark_for_invalidation = mode != kParentOnlyInvalidation;
// Invalidate clients registered on the this object (via SVGResources).
for (auto* client : clients_) {
DCHECK(client->IsSVG());
if (client->IsSVGResourceContainer()) {
ToLayoutSVGResourceContainer(client)->RemoveAllClientsFromCache(
mark_for_invalidation);
continue;
}
if (mark_for_invalidation)
MarkClientForInvalidation(client, mode);
LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
client, needs_layout);
}
// Invalidate clients registered via an SVGElementProxy.
NotifyContentChanged();
is_invalidating_ = false;
}
void LayoutSVGResourceContainer::MarkClientForInvalidation(
LayoutObject* client,
InvalidationMode mode) {
DCHECK(client);
DCHECK(!clients_.IsEmpty());
switch (mode) {
case kLayoutAndBoundariesInvalidation:
case kBoundariesInvalidation:
client->SetNeedsBoundariesUpdate();
break;
case kPaintInvalidation:
// Since LayoutSVGInlineTexts don't have SVGResources (they use their
// parent's), they will not be notified of changes to paint servers. So
// if the client is one that could have a LayoutSVGInlineText use a
// paint invalidation reason that will force paint invalidation of the
// entire <text>/<tspan>/... subtree.
client->SetShouldDoFullPaintInvalidation(
PaintInvalidationReason::kSVGResource);
// Invalidate paint properties to update effects if any.
client->SetNeedsPaintPropertyUpdate();
break;
case kParentOnlyInvalidation:
break;
}
}
void LayoutSVGResourceContainer::AddClient(LayoutObject* client) {
DCHECK(client);
clients_.insert(client);
ClearInvalidationMask();
}
void LayoutSVGResourceContainer::RemoveClient(LayoutObject* client) {
DCHECK(client);
RemoveClientFromCache(client, false);
clients_.erase(client);
}
void LayoutSVGResourceContainer::InvalidateCacheAndMarkForLayout(
SubtreeLayoutScope* layout_scope) {
if (SelfNeedsLayout())
return;
SetNeedsLayoutAndFullPaintInvalidation(
LayoutInvalidationReason::kSvgResourceInvalidated, kMarkContainerChain,
layout_scope);
if (EverHadLayout())
RemoveAllClientsFromCache();
}
static inline void RemoveFromCacheAndInvalidateDependencies(
LayoutObject* object,
bool needs_layout) {
DCHECK(object);
if (SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(object)) {
resources->RemoveClientFromCacheAffectingObjectBounds(object);
}
if (!object->GetNode() || !object->GetNode()->IsSVGElement())
return;
SVGElementSet* dependencies =
ToSVGElement(object->GetNode())->SetOfIncomingReferences();
if (!dependencies)
return;
// We allow cycles in SVGDocumentExtensions reference sets in order to avoid
// expensive reference graph adjustments on changes, so we need to break
// possible cycles here.
// This strong reference is safe, as it is guaranteed that this set will be
// emptied at the end of recursion.
DEFINE_STATIC_LOCAL(SVGElementSet, invalidating_dependencies,
(new SVGElementSet));
for (SVGElement* element : *dependencies) {
if (LayoutObject* layout_object = element->GetLayoutObject()) {
if (UNLIKELY(!invalidating_dependencies.insert(element).is_new_entry)) {
// Reference cycle: we are in process of invalidating this dependant.
continue;
}
LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
layout_object, needs_layout);
invalidating_dependencies.erase(element);
}
}
}
void LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
LayoutObject* object,
bool needs_layout) {
DCHECK(object);
DCHECK(object->GetNode());
if (needs_layout && !object->DocumentBeingDestroyed())
object->SetNeedsLayoutAndFullPaintInvalidation(
LayoutInvalidationReason::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.
ToLayoutSVGResourceContainer(current)->RemoveAllClientsFromCache();
break;
}
current = current->Parent();
}
}
} // namespace blink