// 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(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(execution_context); DCHECK(!document.Parsing()); // Processing instruction (XML documents only). // We don't support linking to embedded CSS stylesheets, // see 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 processing_instruction_; }; DocumentXSLT::DocumentXSLT(Document& document) : Supplement(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::From(document); } DocumentXSLT& DocumentXSLT::From(Document& document) { DocumentXSLT* supplement = Supplement::From(document); if (!supplement) { supplement = MakeGarbageCollected(document); Supplement::ProvideTo(document, supplement); } return *supplement; } void DocumentXSLT::Trace(blink::Visitor* visitor) { visitor->Trace(transform_source_document_); Supplement::Trace(visitor); } } // namespace blink