blob: 3dc474582a3f7a90ef16e633091dc35afb5db225 [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 "third_party/blink/renderer/core/xml/document_xslt.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/processing_instruction.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/xml/xsl_style_sheet.h"
#include "third_party/blink/renderer/core/xml/xslt_processor.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
namespace blink {
class DOMContentLoadedListener final
: public NativeEventListener,
public ProcessingInstruction::DetachableEventListener {
USING_GARBAGE_COLLECTED_MIXIN(DOMContentLoadedListener);
public:
static DOMContentLoadedListener* Create(ProcessingInstruction* pi) {
return MakeGarbageCollected<DOMContentLoadedListener>(pi);
}
DOMContentLoadedListener(ProcessingInstruction* pi)
: processing_instruction_(pi) {}
void Invoke(ExecutionContext* execution_context, Event* event) override {
DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
DCHECK_EQ(event->type(), "DOMContentLoaded");
Document& document = *To<Document>(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; }
void Trace(blink::Visitor* visitor) override {
visitor->Trace(processing_instruction_);
NativeEventListener::Trace(visitor);
ProcessingInstruction::DetachableEventListener::Trace(visitor);
}
private:
// 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(event_type_names::kDOMContentLoaded, 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(event_type_names::kDOMContentLoaded,
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;
}
// static
const char DocumentXSLT::kSupplementName[] = "DocumentXSLT";
bool DocumentXSLT::HasTransformSourceDocument(Document& document) {
return Supplement<Document>::From<DocumentXSLT>(document);
}
DocumentXSLT& DocumentXSLT::From(Document& document) {
DocumentXSLT* supplement = Supplement<Document>::From<DocumentXSLT>(document);
if (!supplement) {
supplement = MakeGarbageCollected<DocumentXSLT>(document);
Supplement<Document>::ProvideTo(document, supplement);
}
return *supplement;
}
void DocumentXSLT::Trace(blink::Visitor* visitor) {
visitor->Trace(transform_source_document_);
Supplement<Document>::Trace(visitor);
}
} // namespace blink