blob: d804bc95d6dcbd84698a8d94c9bc09f020bb985e [file] [log] [blame]
// Copyright 2014 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 "core/xml/DocumentXSLT.h"
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/dom/Document.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/Node.h"
#include "core/dom/ProcessingInstruction.h"
#include "core/dom/events/Event.h"
#include "core/dom/events/EventListener.h"
#include "core/frame/UseCounter.h"
#include "core/probe/CoreProbes.h"
#include "core/xml/XSLStyleSheet.h"
#include "core/xml/XSLTProcessor.h"
#include "platform/bindings/DOMWrapperWorld.h"
#include "platform/bindings/ScriptState.h"
namespace blink {
class DOMContentLoadedListener final
: public EventListener,
public ProcessingInstruction::DetachableEventListener {
USING_GARBAGE_COLLECTED_MIXIN(DOMContentLoadedListener);
public:
static DOMContentLoadedListener* Create(ProcessingInstruction* pi) {
return new DOMContentLoadedListener(pi);
}
bool operator==(const EventListener& rhs) const override {
return this == &rhs;
}
void handleEvent(ExecutionContext* execution_context, Event* event) override {
DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
DCHECK_EQ(event->type(), "DOMContentLoaded");
Document& document = *ToDocument(execution_context);
DCHECK(!document.Parsing());
// Processing instruction (XML documents only).
// We don't support linking to embedded CSS stylesheets,
// see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
// Don't apply XSL transforms to already transformed documents.
if (DocumentXSLT::HasTransformSourceDocument(document))
return;
ProcessingInstruction* pi = DocumentXSLT::FindXSLStyleSheet(document);
if (!pi || pi != processing_instruction_ || pi->IsLoading())
return;
DocumentXSLT::ApplyXSLTransform(document, pi);
}
void Detach() override { processing_instruction_ = nullptr; }
EventListener* ToEventListener() override { return this; }
virtual void Trace(blink::Visitor* visitor) {
visitor->Trace(processing_instruction_);
EventListener::Trace(visitor);
ProcessingInstruction::DetachableEventListener::Trace(visitor);
}
private:
DOMContentLoadedListener(ProcessingInstruction* pi)
: EventListener(EventListener::kCPPEventListenerType),
processing_instruction_(pi) {}
// If this event listener is attached to a ProcessingInstruction, keep a
// weak reference back to it. That ProcessingInstruction is responsible for
// detaching itself and clear out the reference.
Member<ProcessingInstruction> processing_instruction_;
};
DocumentXSLT::DocumentXSLT(Document& document)
: Supplement<Document>(document), transform_source_document_(nullptr) {}
void DocumentXSLT::ApplyXSLTransform(Document& document,
ProcessingInstruction* pi) {
DCHECK(!pi->IsLoading());
UseCounter::Count(document, WebFeature::kXSLProcessingInstruction);
XSLTProcessor* processor = XSLTProcessor::Create(document);
processor->SetXSLStyleSheet(ToXSLStyleSheet(pi->sheet()));
String result_mime_type;
String new_source;
String result_encoding;
document.SetParsingState(Document::kParsing);
if (!processor->TransformToString(&document, result_mime_type, new_source,
result_encoding)) {
document.SetParsingState(Document::kFinishedParsing);
return;
}
// FIXME: If the transform failed we should probably report an error (like
// Mozilla does).
LocalFrame* owner_frame = document.GetFrame();
processor->CreateDocumentFromSource(new_source, result_encoding,
result_mime_type, &document, owner_frame);
probe::frameDocumentUpdated(owner_frame);
document.SetParsingState(Document::kFinishedParsing);
}
ProcessingInstruction* DocumentXSLT::FindXSLStyleSheet(Document& document) {
for (Node* node = document.firstChild(); node; node = node->nextSibling()) {
if (node->getNodeType() != Node::kProcessingInstructionNode)
continue;
ProcessingInstruction* pi = ToProcessingInstruction(node);
if (pi->IsXSL())
return pi;
}
return nullptr;
}
bool DocumentXSLT::ProcessingInstructionInsertedIntoDocument(
Document& document,
ProcessingInstruction* pi) {
if (!pi->IsXSL())
return false;
if (!RuntimeEnabledFeatures::XSLTEnabled() || !document.GetFrame())
return true;
DOMContentLoadedListener* listener = DOMContentLoadedListener::Create(pi);
document.addEventListener(EventTypeNames::DOMContentLoaded, listener, false);
DCHECK(!pi->EventListenerForXSLT());
pi->SetEventListenerForXSLT(listener);
return true;
}
bool DocumentXSLT::ProcessingInstructionRemovedFromDocument(
Document& document,
ProcessingInstruction* pi) {
if (!pi->IsXSL())
return false;
if (!pi->EventListenerForXSLT())
return true;
DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
document.removeEventListener(EventTypeNames::DOMContentLoaded,
pi->EventListenerForXSLT(), false);
pi->ClearEventListenerForXSLT();
return true;
}
bool DocumentXSLT::SheetLoaded(Document& document, ProcessingInstruction* pi) {
if (!pi->IsXSL())
return false;
if (RuntimeEnabledFeatures::XSLTEnabled() && !document.Parsing() &&
!pi->IsLoading() && !DocumentXSLT::HasTransformSourceDocument(document)) {
if (FindXSLStyleSheet(document) == pi)
ApplyXSLTransform(document, pi);
}
return true;
}
const char* DocumentXSLT::SupplementName() {
return "DocumentXSLT";
}
bool DocumentXSLT::HasTransformSourceDocument(Document& document) {
return static_cast<DocumentXSLT*>(
Supplement<Document>::From(document, SupplementName()));
}
DocumentXSLT& DocumentXSLT::From(Document& document) {
DocumentXSLT* supplement = static_cast<DocumentXSLT*>(
Supplement<Document>::From(document, SupplementName()));
if (!supplement) {
supplement = new DocumentXSLT(document);
Supplement<Document>::ProvideTo(document, SupplementName(), supplement);
}
return *supplement;
}
void DocumentXSLT::Trace(blink::Visitor* visitor) {
visitor->Trace(transform_source_document_);
Supplement<Document>::Trace(visitor);
}
} // namespace blink