blob: 7782d55cadcb97ac6ba91f6f7e96e566cb8f9419 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/svg/svg_resource.h"
#include "services/network/public/mojom/content_security_policy.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/id_target_observer.h"
#include "third_party/blink/renderer/core/dom/tree_scope.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
#include "third_party/blink/renderer/core/svg/svg_resource_client.h"
#include "third_party/blink/renderer/core/svg/svg_resource_document_content.h"
#include "third_party/blink/renderer/core/svg/svg_uri_reference.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
namespace blink {
SVGResource::SVGResource() = default;
SVGResource::~SVGResource() = default;
void SVGResource::Trace(Visitor* visitor) const {
visitor->Trace(target_);
visitor->Trace(clients_);
}
void SVGResource::AddClient(SVGResourceClient& client) {
auto& entry = clients_.insert(&client, ClientEntry()).stored_value->value;
entry.count++;
entry.cached_cycle_check = kNeedCheck;
if (LayoutSVGResourceContainer* container = ResourceContainerNoCycleCheck())
container->ClearInvalidationMask();
}
void SVGResource::RemoveClient(SVGResourceClient& client) {
auto it = clients_.find(&client);
DCHECK(it != clients_.end());
it->value.count--;
if (it->value.count)
return;
clients_.erase(it);
// The last instance of |client| was removed. Clear its entry in
// resource's cache.
if (LayoutSVGResourceContainer* container = ResourceContainerNoCycleCheck())
container->RemoveClientFromCache(client);
}
void SVGResource::InvalidateCycleCache() {
for (auto& client_entry : clients_.Values())
client_entry.cached_cycle_check = kNeedCheck;
}
void SVGResource::NotifyContentChanged() {
InvalidateCycleCache();
HeapVector<Member<SVGResourceClient>> clients;
CopyKeysToVector(clients_, clients);
for (SVGResourceClient* client : clients)
client->ResourceContentChanged(this);
}
LayoutSVGResourceContainer* SVGResource::ResourceContainerNoCycleCheck() const {
if (!target_)
return nullptr;
return DynamicTo<LayoutSVGResourceContainer>(target_->GetLayoutObject());
}
LayoutSVGResourceContainer* SVGResource::ResourceContainer(
SVGResourceClient& client) const {
auto it = clients_.find(&client);
if (it == clients_.end())
return nullptr;
auto* container = ResourceContainerNoCycleCheck();
if (!container)
return nullptr;
ClientEntry& entry = it->value;
if (entry.cached_cycle_check == kNeedCheck) {
entry.cached_cycle_check = kPerformingCheck;
bool has_cycle = container->FindCycle();
DCHECK_EQ(entry.cached_cycle_check, kPerformingCheck);
entry.cached_cycle_check = has_cycle ? kHasCycle : kNoCycle;
}
if (entry.cached_cycle_check == kHasCycle)
return nullptr;
DCHECK_EQ(entry.cached_cycle_check, kNoCycle);
return container;
}
bool SVGResource::FindCycle(SVGResourceClient& client) const {
auto it = clients_.find(&client);
if (it == clients_.end())
return false;
auto* container = ResourceContainerNoCycleCheck();
if (!container)
return false;
ClientEntry& entry = it->value;
switch (entry.cached_cycle_check) {
case kNeedCheck: {
entry.cached_cycle_check = kPerformingCheck;
bool has_cycle = container->FindCycle();
DCHECK_EQ(entry.cached_cycle_check, kPerformingCheck);
// Update our cached state based on the result of FindCycle(), but don't
// signal a cycle since ResourceContainer() will consider the resource
// invalid if one is present, thus we break the cycle at this resource.
entry.cached_cycle_check = has_cycle ? kHasCycle : kNoCycle;
return false;
}
case kPerformingCheck:
// If we're on the current checking path, signal a cycle.
return true;
case kHasCycle:
case kNoCycle:
// We have a cached result, but don't signal a cycle since
// ResourceContainer() will consider the resource invalid if one is
// present.
return false;
}
}
LocalSVGResource::LocalSVGResource(TreeScope& tree_scope,
const AtomicString& id)
: tree_scope_(tree_scope) {
target_ = SVGURIReference::ObserveTarget(
id_observer_, tree_scope, id,
WTF::BindRepeating(&LocalSVGResource::TargetChanged,
WrapWeakPersistent(this), id));
}
void LocalSVGResource::Unregister() {
SVGURIReference::UnobserveTarget(id_observer_);
}
void LocalSVGResource::NotifyFilterPrimitiveChanged(
SVGFilterPrimitiveStandardAttributes& primitive,
const QualifiedName& attribute) {
HeapVector<Member<SVGResourceClient>> clients;
CopyKeysToVector(clients_, clients);
for (SVGResourceClient* client : clients)
client->FilterPrimitiveChanged(this, primitive, attribute);
}
void LocalSVGResource::TargetChanged(const AtomicString& id) {
Element* new_target = tree_scope_->getElementById(id);
if (new_target == target_)
return;
// Clear out caches on the old resource, and then notify clients about the
// change.
LayoutSVGResourceContainer* old_resource = ResourceContainerNoCycleCheck();
if (old_resource)
old_resource->RemoveAllClientsFromCache();
target_ = new_target;
NotifyContentChanged();
}
void LocalSVGResource::Trace(Visitor* visitor) const {
visitor->Trace(tree_scope_);
visitor->Trace(id_observer_);
SVGResource::Trace(visitor);
}
ExternalSVGResource::ExternalSVGResource(const KURL& url) : url_(url) {}
void ExternalSVGResource::Load(Document& document) {
if (document_content_)
return;
ExecutionContext* execution_context = document.GetExecutionContext();
ResourceLoaderOptions options(execution_context->GetCurrentWorld());
options.initiator_info.name = fetch_initiator_type_names::kCSS;
FetchParameters params(ResourceRequest(url_), options);
document_content_ = SVGResourceDocumentContent::Fetch(params, document, this);
target_ = ResolveTarget();
}
void ExternalSVGResource::LoadWithoutCSP(Document& document) {
if (document_content_)
return;
ExecutionContext* execution_context = document.GetExecutionContext();
ResourceLoaderOptions options(execution_context->GetCurrentWorld());
options.initiator_info.name = fetch_initiator_type_names::kCSS;
FetchParameters params(ResourceRequest(url_), options);
params.SetContentSecurityCheck(
network::mojom::blink::CSPDisposition::DO_NOT_CHECK);
document_content_ = SVGResourceDocumentContent::Fetch(params, document, this);
target_ = ResolveTarget();
}
void ExternalSVGResource::NotifyFinished(Resource*) {
Element* new_target = ResolveTarget();
if (new_target == target_)
return;
target_ = new_target;
NotifyContentChanged();
}
String ExternalSVGResource::DebugName() const {
return "ExternalSVGResource";
}
Element* ExternalSVGResource::ResolveTarget() {
if (!document_content_)
return nullptr;
if (!url_.HasFragmentIdentifier())
return nullptr;
Document* external_document = document_content_->GetDocument();
if (!external_document)
return nullptr;
AtomicString decoded_fragment(DecodeURLEscapeSequences(
url_.FragmentIdentifier(), DecodeURLMode::kUTF8OrIsomorphic));
return external_document->getElementById(decoded_fragment);
}
void ExternalSVGResource::Trace(Visitor* visitor) const {
visitor->Trace(document_content_);
SVGResource::Trace(visitor);
ResourceClient::Trace(visitor);
}
} // namespace blink